diff --git a/.ai/README.md b/.ai/README.md new file mode 100644 index 0000000000..a0f58df44e --- /dev/null +++ b/.ai/README.md @@ -0,0 +1,3 @@ +# `.ai/` + +Everything an AI agent needs is in [`dev-docs/`](../dev-docs/README.md). This directory holds nothing else. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..8fdee8bb0c --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,24 @@ +{ + "enabledPlugins": [ + "typescript-lsp@claude-plugins-official" + ], + "permissions": { + "deny": [ + "Read(/hyperformula/lib/**)", + "Read(/hyperformula/es/**)", + "Read(/hyperformula/commonjs/**)", + "Read(/hyperformula/dist/**)", + "Read(/hyperformula/languages/**)", + "Read(/hyperformula/typings/**)", + "Read(/hyperformula/coverage/**)", + "Read(/hyperformula/test-jest/**)", + "Read(/hyperformula/test-jasmine/**)", + "Read(/hyperformula/typedoc/**)", + "Read(/hyperformula/storage/**)", + "Read(/docs/functions/**)", + "Read(/docs/.vuepress/dist/**)", + "Read(/docs/.vuepress/api-sidebar.json)", + "Read(/docs/.vuepress/api-sidebar-relative.json)" + ] + } +} diff --git a/.claude/skills/changelog-creation/SKILL.md b/.claude/skills/changelog-creation/SKILL.md new file mode 100644 index 0000000000..4ccbc4ce26 --- /dev/null +++ b/.claude/skills/changelog-creation/SKILL.md @@ -0,0 +1,31 @@ +--- +name: changelog-creation +description: Use when a change to source code needs a changelog entry, and before pushing any bug fix, feature, or behaviour change. Covers when an entry is required, which section it belongs in, how to write the title, and the link format. +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`DOC-STANDARDS.md`](../../../dev-docs/DOC-STANDARDS.md#the-changelog) | Which section to use, the bullet format, and how to write the text | +| [`PULL-REQUESTS.md`](../../../dev-docs/PULL-REQUESTS.md#order-of-operations) | Why the entry comes after the pull request, not before | + +## 2. Decide whether an entry is required at all + +Documentation-only, test-only, and CI or tooling changes take none, and neither does a bug that was introduced and never released. + +## 3. Open the pull request first + +**Every entry ends with a GitHub link**: the public issue it fixes when one exists, otherwise the pull request. So the entry needs a number that only exists once the pull request is open. Do not guess it — read it from the URL `gh pr create` prints. Skill `pr-creation`. + +## 4. Write the bullet + +Under `## [Unreleased]` in `CHANGELOG.md` at the repository root — one changelog for every package, because they release together on one version. Put it in the section that matches the change, creating the `### ` heading if it is absent. Name the package the entry concerns when the text does not make it obvious. End it with the link from step 3. + +## 5. Re-read it as a user would + +If it names a class, a file, or an internal identifier, rewrite it. Check it carries nothing sensitive — no client, customer, or partner names, and nothing that identifies them indirectly. See [`AGENTS.md`](../../../AGENTS.md#never-publish-sensitive-information). + +## 6. Push it to the same branch + +So the open pull request picks it up. diff --git a/.claude/skills/hyperformula-code-review/SKILL.md b/.claude/skills/hyperformula-code-review/SKILL.md new file mode 100644 index 0000000000..44bcad1e30 --- /dev/null +++ b/.claude/skills/hyperformula-code-review/SKILL.md @@ -0,0 +1,52 @@ +--- +name: hyperformula-code-review +description: Use when reviewing a diff, a branch, or a pull request in the HyperFormula repository. Covers correctness for a calculation engine, performance on the hot paths, the five places a function change must touch, API stability, and what the definition of done requires. +--- + +## 1. Read the relevant files from `dev-docs/` + +Always: + +| File | Why | +|---|---| +| [`DEFINITION-OF-DONE.md`](../../../dev-docs/DEFINITION-OF-DONE.md) | What the change was required to contain | +| [`CODE-STYLE.md`](../../../dev-docs/CODE-STYLE.md) | Style, and which paths are hot enough that complexity matters | +| [`TESTING.md`](../../../dev-docs/TESTING.md#a-test-must-prove-behaviour) | Whether the tests prove anything, or only execute code | + +Then the page covering what the diff touches: [`ARCHITECTURE.md`](../../../hyperformula/dev-docs/ARCHITECTURE.md), [`PARSER.md`](../../../hyperformula/dev-docs/PARSER.md), [`INTERPRETER.md`](../../../hyperformula/dev-docs/INTERPRETER.md), [`DEPENDENCY-GRAPH.md`](../../../hyperformula/dev-docs/DEPENDENCY-GRAPH.md), [`FUNCTION-CATALOGUE.md`](../../../hyperformula/dev-docs/FUNCTION-CATALOGUE.md), [`I18N.md`](../../../hyperformula/dev-docs/I18N.md). + +Review in the order below, and stop to report the first serious finding rather than burying it under style notes. + +## 2. Correctness + +- **Would the test fail without the fix?** Ask it of every bug-fix pull request. +- **Any `throw` reachable from evaluation**, instead of a returned `CellError`. +- **Hand-rolled coercion** instead of `ArithmeticHelper`. +- **Empty cells, empty ranges, and error arguments** — the most common gap in a function change. +- **A parser change without a matching `Unparser` change.** +- **A structural change that does not assert the formula text afterwards.** +- **A new mutation missing one of `CrudOperations`, `Operations`, `UndoRedo`** — undo diverges silently. + +## 3. Completeness of a function change + +Check every one of [the five places a function change must touch](../../../hyperformula/dev-docs/INTERPRETER.md#the-five-places-a-function-change-must-touch); most of them fail silently when missed. Plus `sizeOfResultArrayMethod` for anything array-returning, and an explicit `optionalArg` where arity does not express the valid call. Skill `hyperformula-function-dev`. + +## 4. Performance + +Allocation in a per-cell or per-vertex loop; work that could be hoisted out of the broadcast path; a range expanded into per-cell iteration; anything that widens what a change invalidates; a `ParserWithCaching` change that makes the result depend on something outside the cache key. Ask for `npm run test:performance` on hot-path changes. + +## 5. Public API + +`hyperformula/src/HyperFormula.ts` and its exported types are the contract. A signature, return-type, or behaviour change is breaking and needs a migration-guide section and an explicit note. JSDoc here is published output — review it as documentation. + +## 6. Process + +One atomic change per pull request. Say so when unrelated refactors have been folded in, rather than approving them through. + +## 7. Style, last and briefly + +ESLint owns formatting. Comment only on what it cannot check: a misleading name, a function doing two things, duplicated logic an existing helper already covers. + +## Reporting + +One line per finding: what is wrong, where, and what to do instead. No praise, no summary of what the pull request does. Separate "this is a bug" from "I would have done it differently", and never present the second as the first. diff --git a/.claude/skills/hyperformula-dev/SKILL.md b/.claude/skills/hyperformula-dev/SKILL.md new file mode 100644 index 0000000000..587af24066 --- /dev/null +++ b/.claude/skills/hyperformula-dev/SKILL.md @@ -0,0 +1,69 @@ +--- +name: hyperformula-dev +paths: hyperformula/src/** +description: > + Use for ANY work touching the HyperFormula engine in `hyperformula/src/`: fixing bugs, adding features, + changing the public API, working on the parser, the interpreter, the dependency graph, + CRUD operations, configuration options, named expressions, or number and date formats. + Also use for how-to questions about engine internals (how recalculation works, why a formula + returns an error, where a value is coerced). Triggers on file paths under `hyperformula/src/`, or when the + user describes a symptom in a calculation without naming a file. This is the primary entry + point for engine development — when in doubt, load it. +--- + +## 1. Read the relevant files from `dev-docs/` + +Always: + +| File | Why | +|---|---| +| [`ARCHITECTURE.md`](../../../hyperformula/dev-docs/ARCHITECTURE.md) | The pipeline, the core modules, and the invariants that hold everywhere in `hyperformula/src/` | +| [`CODE-STYLE.md`](../../../dev-docs/CODE-STYLE.md) | Style, and which paths are hot enough that complexity matters | +| [`DEFINITION-OF-DONE.md`](../../../dev-docs/DEFINITION-OF-DONE.md) | What the change must contain before review | + +Then the page for the stage you are changing: + +| File | For | +|---|---| +| [`PARSER.md`](../../../hyperformula/dev-docs/PARSER.md) | `hyperformula/src/parser/` — formula text to AST, and back | +| [`INTERPRETER.md`](../../../hyperformula/dev-docs/INTERPRETER.md) | `hyperformula/src/interpreter/` — AST to value, and built-in functions | +| [`DEPENDENCY-GRAPH.md`](../../../hyperformula/dev-docs/DEPENDENCY-GRAPH.md) | `hyperformula/src/DependencyGraph/` — dependency tracking and recalculation order | +| [`FUNCTION-CATALOGUE.md`](../../../hyperformula/dev-docs/FUNCTION-CATALOGUE.md) | `hyperformula/src/interpreter/functionMetadata/` — function descriptions | +| [`I18N.md`](../../../hyperformula/dev-docs/I18N.md) | `hyperformula/src/i18n/` — function-name translations | +| [`TESTING.md`](../../../hyperformula/dev-docs/TESTING.md) | Writing the test the change needs | + +## 2. Locate the stage before changing anything + +The engine is a pipeline: `CellContentParser` → `parser/` → `GraphBuilder` → `DependencyGraph/` → `Evaluator` → `interpreter/` → `Serialization`. + +| Symptom | Stage | +|---|---| +| Does not parse, or parses wrongly | `hyperformula/src/parser/` | +| `getCellFormula` returns something the user never typed | `hyperformula/src/parser/Unparser.ts` | +| A function returns the wrong value or error | `hyperformula/src/interpreter/plugin/` — skill `hyperformula-function-dev` | +| Value right, but stale after an edit | `hyperformula/src/DependencyGraph/`, `hyperformula/src/Evaluator.ts` | +| Wrong after adding or removing rows or columns | `hyperformula/src/dependencyTransformers/`, `LazilyTransformingAstService.ts` | +| Wrong in one language only | `hyperformula/src/i18n/languages/` — skill `i18n-translations` | +| Coercion or comparison is wrong | `hyperformula/src/interpreter/ArithmeticHelper.ts` | +| The public API disagrees with its docs | `hyperformula/src/HyperFormula.ts` | + +A bug that looks like an interpreter problem is often a parser or graph problem. Confirm which before editing. Use the `typescript-lsp` plugin to find a definition or its callers; grep is for text, not symbols. + +## 3. Reproduce first + +Write the failing test before the fix and watch it fail — skill `test-writing-discipline`. For a calculation bug the smallest reproduction is a two-line `buildFromArray` plus one `getCellValue`. + +If `hyperformula/test/hyperformula-tests/` is absent, `npm run test:jest` runs only the smoke tests and reports a clean pass over almost nothing. Run `npm run test:setup-private` first, and after every branch switch. + +## 4. Change, then run the fast loop + +```bash +npm run test:jest -- +npm run lint +``` + +Run `npm run test:performance` for changes to the evaluation or CRUD hot paths. + +## 5. Finish the change + +Tests, documentation, JSDoc, changelog, translations — every item of `DEFINITION-OF-DONE.md`. diff --git a/.claude/skills/hyperformula-function-dev/SKILL.md b/.claude/skills/hyperformula-function-dev/SKILL.md new file mode 100644 index 0000000000..4fc8aa2686 --- /dev/null +++ b/.claude/skills/hyperformula-function-dev/SKILL.md @@ -0,0 +1,38 @@ +--- +name: hyperformula-function-dev +paths: hyperformula/src/interpreter/** +description: Use when adding a new built-in spreadsheet function to HyperFormula, changing an existing one's signature, arguments, return type, or error behaviour, or when a function returns the wrong value or the wrong error. Covers the FunctionPlugin contract, runFunction and argument metadata, the function metadata catalogue, translations, and the full end-to-end checklist. +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`INTERPRETER.md`](../../../hyperformula/dev-docs/INTERPRETER.md#built-in-functions) | The plugin contract, `runFunction`, and every argument and function metadata field. Read this before writing any code. | +| [`FUNCTION-CATALOGUE.md`](../../../hyperformula/dev-docs/FUNCTION-CATALOGUE.md) | What the catalogue entry must contain, and the two ways to get it wrong | +| [`I18N.md`](../../../hyperformula/dev-docs/I18N.md) | Where to source a translation, and why an invented one cannot be taken back | +| [`TESTING.md`](../../../hyperformula/dev-docs/TESTING.md#what-each-kind-of-change-needs) | The list of cases a function change must cover | +| [`DEFINITION-OF-DONE.md`](../../../dev-docs/DEFINITION-OF-DONE.md) | What the change must contain before review | + +## 2. Touch all five places + +A function is not done until all five agree, and they do not fail the same way — a missing catalogue entry fails the docs build, a parameter-count mismatch only warns on the console, and the rest fail silently. + +The list is in [`INTERPRETER.md`](../../../hyperformula/dev-docs/INTERPRETER.md#the-five-places-a-function-change-must-touch). Work through it there rather than from a copy; two of the five have their own page, linked from it. + +## 3. Declare the two things nothing cross-checks + +- A function that can return an array needs `sizeOfResultArrayMethod`. +- A function whose valid call arity alone does not express — a zero-argument form, an omitted trailing argument — needs `optionalArg: true` declared explicitly, or the public API advertises the argument as required. + +## 4. Verify + +```bash +npm run test:jest -- +npm run docs:generate-function-docs # fails loudly on a bad or missing catalogue entry +npm run lint +``` + +## 5. Record any deviation from Excel + +That is a decision, not an accident. Put it in [`docs/guide/list-of-differences.md`](../../../docs/guide/list-of-differences.md) and say so in the changelog entry. Never write a description that documents Excel while the code does something else. diff --git a/.claude/skills/hyperformula-unit-testing/SKILL.md b/.claude/skills/hyperformula-unit-testing/SKILL.md new file mode 100644 index 0000000000..affe9ed77b --- /dev/null +++ b/.claude/skills/hyperformula-unit-testing/SKILL.md @@ -0,0 +1,40 @@ +--- +name: hyperformula-unit-testing +paths: hyperformula/test/** +description: Use when writing or modifying tests for HyperFormula, or when a change to `hyperformula/src/` needs test coverage. Covers the two suites, fetching the private suite, how to build an engine in a test, and what a function or CRUD change must cover. +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`TESTING.md`](../../../hyperformula/dev-docs/TESTING.md) | The two suites, how to run them, and what each kind of change must cover | +| [`TESTING.md`](../../../dev-docs/TESTING.md#how-to-write-a-test-case) | How to write the case itself, and what a test must prove to count | +| [`WORKTREES.md`](../../../dev-docs/WORKTREES.md) | Only when working in a linked worktree, where the private suite is absent entirely | + +And [`hyperformula/test/README.md`](../../../hyperformula/test/README.md) for how the private suite is fetched and the environment variables it honours. + +## 2. Attach the private suite before trusting anything + +```bash +npm run test:setup-private +``` + +Run it after every branch switch. Without `hyperformula/test/hyperformula-tests/` the Jest run covers only the smoke tests and reports a clean pass over almost nothing — the most common false signal in this repository. `test:performance` and `test:compatibility` fail on a missing path rather than an assertion; read the error before concluding the code is broken. + +## 3. Write the case from the requirement + +Not from the implementation. A test written from the code passes for any implementation, including the wrong one. + +## 4. Run it and watch it fail + +For a bug fix this is not optional — skill `test-writing-discipline`. + +```bash +npm run test:jest -- # one file or one describe +npm run test:watch +``` + +## 5. Fix the code, then run again + +Read the output rather than assuming it. `npm run test` is the full local gate: lint, Jest, and the browser run. diff --git a/.claude/skills/i18n-translations/SKILL.md b/.claude/skills/i18n-translations/SKILL.md new file mode 100644 index 0000000000..84b729a067 --- /dev/null +++ b/.claude/skills/i18n-translations/SKILL.md @@ -0,0 +1,35 @@ +--- +name: i18n-translations +paths: hyperformula/src/i18n/** +description: Use when adding a built-in function that needs translated names, adding a language pack, or fixing a function name that is wrong in one language. Covers the translation sources, the rules, and what breaks when a key is missing. +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`I18N.md`](../../../hyperformula/dev-docs/I18N.md) | Why translations are engine input rather than decoration, the rules, and the table of sources to translate from | +| [`PARSER.md`](../../../hyperformula/dev-docs/PARSER.md) | Only when changing separators or error literals — the lexer builds its token set from the language package | + +## 2. Look the name up in a real source + +Use the sources in [`I18N.md`](../../../hyperformula/dev-docs/I18N.md#where-to-find-a-translation), in the order listed. **Never invent or machine-translate a function name.** A wrong one ships to every user of that language pack and cannot be changed without breaking their formulas. + +## 3. Add the key to every language file + +All of them, in the same change as the function. Missing one is the usual failure, and nothing type-checks it on every path. Do not reorder existing entries while adding one — it turns a one-line diff into an unreviewable one. + +## 4. Test it in that language + +A test that parses a formula using the translated name and asserts the result. Not in English. + +## 5. Verify + +```bash +npm run test:jest -- i18n +npm run lint +``` + +## Adding a whole language pack + +The file, its export in `hyperformula/src/i18n/languages/index.ts`, a key set identical to the other packs, a changelog entry, and `npm run bundle:languages --workspace=hyperformula` for the standalone UMD build. diff --git a/.claude/skills/pr-creation/SKILL.md b/.claude/skills/pr-creation/SKILL.md new file mode 100644 index 0000000000..017a3a86d9 --- /dev/null +++ b/.claude/skills/pr-creation/SKILL.md @@ -0,0 +1,42 @@ +--- +name: pr-creation +description: Use before creating, pushing, opening, or updating a pull request in the HyperFormula repository — load this BEFORE running `gh pr create` or pushing a feature/docs/fix branch, not only when the user says "PR". Covers branch naming, the pre-flight lint/tests, the PR-then-changelog flow, and filling the GitHub PR template. +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`PULL-REQUESTS.md`](../../../dev-docs/PULL-REQUESTS.md) | Branch naming, the pre-flight gate, the template, and the one-change-per-pull-request rule | +| [`DEFINITION-OF-DONE.md`](../../../dev-docs/DEFINITION-OF-DONE.md) | Every item the change must contain before review | +| [`DOC-STANDARDS.md`](../../../dev-docs/DOC-STANDARDS.md#the-changelog) | The changelog entry that follows the pull request | + +## 2. Commit on a correctly named branch + +`/-`. Nothing from a private ticket in the name — the identifier alone is fine. + +## 3. Run the gate and read the output + +```bash +npm run test:setup-private +npm run lint +npm run test:jest +``` + +A green Jest run without `hyperformula/test/hyperformula-tests/` covers only the smoke tests. Confirm the suite is attached before calling it green. Do not open a pull request on a red run and describe it as ready. + +## 4. Push and open the pull request + +Fill in every section of the template. Tick the Types of changes boxes honestly, breaking change included. + +## 5. Add the changelog entry + +Read the number from the pull request URL, then skill `changelog-creation`. Push it to the same branch. + +## 6. Confirm the definition of done + +Then read your own diff end to end before asking anyone else to. + +## While the branch is open + +Update the description in the same push whenever the scope changes. diff --git a/.claude/skills/test-writing-discipline/SKILL.md b/.claude/skills/test-writing-discipline/SKILL.md new file mode 100644 index 0000000000..0a1506c7f6 --- /dev/null +++ b/.claude/skills/test-writing-discipline/SKILL.md @@ -0,0 +1,30 @@ +--- +name: test-writing-discipline +description: Use when writing, fixing, or reviewing any test for HyperFormula, and whenever a test is red during feature work. Enforces that tests prove intended behaviour rather than merely execute code, and never go "green for the sake of green". +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`TESTING.md`](../../../dev-docs/TESTING.md#a-test-must-prove-behaviour) | The rule, the banned ways of going green, and what a hollow assertion looks like. It is short — read it now, before touching the test. | + +## 2. Write the test from the requirement + +Before reading the implementation. A test written from the code passes for any implementation, including the wrong one. + +## 3. Run it and watch it fail + +For a bug fix this is not optional: a test that has never failed proves nothing about the bug. + +## 4. Fix the code, not the test + +When a test is red the default assumption is that the code is wrong. Changing the expectation requires a one-sentence reason about the *specification*, not about the effort of fixing the code. + +## 5. Run it again and read the output + +Never claim a test passes because the reasoning is sound. And before treating green as coverage, confirm `hyperformula/test/hyperformula-tests/` is present — `npm run test:setup-private`. + +## If you cannot make it pass honestly + +Say so. "This test fails and I do not yet know why" is a useful report; a green run that hides it is not. diff --git a/.claude/skills/writing-docs-pages/SKILL.md b/.claude/skills/writing-docs-pages/SKILL.md new file mode 100644 index 0000000000..774a956e97 --- /dev/null +++ b/.claude/skills/writing-docs-pages/SKILL.md @@ -0,0 +1,40 @@ +--- +name: writing-docs-pages +paths: docs/** +description: Use when creating or editing a page in the HyperFormula documentation portal, adding a guide, or updating the API reference. Covers what is generated versus hand-written, sidebar registration, running the portal, and the writing rules. +--- + +## 1. Read the relevant files from `dev-docs/` + +| File | Why | +|---|---| +| [`DOCS-CONTENT-GUIDE.md`](../../../dev-docs/DOCS-CONTENT-GUIDE.md) | How to write the page: structure, chunking, language, code examples, VuePress conventions, and the self-review checklist to run before finishing | +| [`DOC-STANDARDS.md`](../../../dev-docs/DOC-STANDARDS.md) | When documentation is required, and describing HyperFormula rather than Excel | +| [`FUNCTION-CATALOGUE.md`](../../../hyperformula/dev-docs/FUNCTION-CATALOGUE.md) | Only when the change concerns the built-in functions page, which is generated from the catalogue | +| [`BUILD.md`](../../../dev-docs/BUILD.md) | Which documentation files are generated, by which command | + +And [`docs/README.md`](../../../dev-docs/README.md), for what the portal contains and how to run it. + +## 2. Change the source, not the output + +| To change | Edit | Then run | +|---|---|---| +| What the functions page says about a function | its catalogue entry in `hyperformula/src/interpreter/functionMetadata/categories/` | `npm run docs:generate-function-docs` | +| The API reference | the JSDoc in `hyperformula/src/` | `npm run typedoc:build-api` | +| A guide | the file in `docs/guide/` | `npm run docs:dev` | + +`docs/guide/built-in-functions.md` and `docs/api/` are git-ignored build output. Editing them is always wrong, and the edit disappears on the next build. + +## 3. Run the portal + +```bash +npm run bundle-all # the portal embeds the built engine +npm run docs:dev # http://localhost:8080/hyperformula/ +``` + +## 4. Before you finish + +- A new page needs a sidebar entry under `docs/.vuepress/`, or it builds and is unreachable. +- Link to the API reference for detail rather than restating it. +- Verify any behavioural claim against the implementation. Where HyperFormula deviates from Excel, record it in [`docs/guide/list-of-differences.md`](../../../docs/guide/list-of-differences.md). +- Run the self-review checklist at the end of [`DOCS-CONTENT-GUIDE.md`](../../../dev-docs/DOCS-CONTENT-GUIDE.md#self-review-checklist-run-before-finishing-any-page). diff --git a/.eslintignore b/.eslintignore index 03546876e2..0ed40f6680 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,24 +5,41 @@ node_modules docs/examples/ # 3rd party -src/interpreter/plugin/3rdparty +hyperformula/src/interpreter/plugin/3rdparty # Configurations *.config.js -karma.* +hyperformula/.config/ +hyperformula/karma.* doc -test/_setupFiles/*.js +hyperformula/test/_setupFiles/*.js + +# Scripts, not linted +script + +# The source language packs, deliberately, and only for now. +# +# The old ignore list carried a bare `languages` entry, meant for the build +# output. An unanchored pattern matches a directory of that name at ANY depth, +# so it also excluded hyperformula/src/i18n/languages/ - and the `sort-keys` +# override that targets those files has therefore never run. Anchoring the +# build-output entry below exposed 881 pre-existing violations in them. +# +# Sorting 19 translation files is a change of its own, not a side effect of +# moving directories around. Delete this entry in that change. +hyperformula/src/i18n/languages # Auto-generated directories -commonjs -coverage -dist +hyperformula/commonjs +hyperformula/coverage +hyperformula/dist +hyperformula/es +hyperformula/languages +hyperformula/lib +hyperformula/test-jasmine +hyperformula/test-jest +hyperformula/typings +docs/.vuepress/dist doc -es -languages -lib -script -test-jasmine -test-jest typedoc -typings + diff --git a/.eslintrc.js b/.eslintrc.js index a5e9976e13..5eaf854bc6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,5 @@ +const path = require('path'); + module.exports = { root: true, ignorePatterns: ['.eslintrc.js'], @@ -15,7 +17,7 @@ module.exports = { }, parserOptions: { tsconfigRootDir: __dirname, - project: './tsconfig.json', + project: './hyperformula/tsconfig.json', createDefaultProgram: true, }, extends: [ @@ -134,7 +136,7 @@ module.exports = { { files: ['**/src/**/*.ts'], rules: { - 'license-header/header': [ 'error', './.config/source-license-header.js' ], + 'license-header/header': [ 'error', path.join(__dirname, 'hyperformula/.config/source-license-header.js') ], } }, { diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 60c43dfc32..5ef21e7919 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -34,4 +34,4 @@ jobs: run: npm ci - name: Build docs - run: npm run docs:build + run: npm run docs:install && npm run docs:build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 936ef53fa2..b745b1a0b4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,10 +36,10 @@ jobs: with: ssh-key: ${{ secrets.DEPLOY_TOKEN }} repository: handsontable/hyperformula-tests - path: test/hyperformula-tests + path: hyperformula/test/hyperformula-tests - name: Fetch hyperformula-tests and sync branches - run: cd test && ./fetch-tests.sh + run: cd hyperformula/test && ./fetch-tests.sh - name: Install dependencies run: npm ci diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 9ef3c8190c..80dd56bb05 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -34,16 +34,16 @@ jobs: with: ssh-key: ${{ secrets.DEPLOY_TOKEN }} repository: handsontable/hyperformula-tests - path: test/hyperformula-tests + path: hyperformula/test/hyperformula-tests - name: Fetch hyperformula-tests and sync branches - run: cd test && ./fetch-tests.sh + run: cd hyperformula/test && ./fetch-tests.sh - name: (base) Install dependencies run: npm ci - name: (base) Run performance tests - run: npm run benchmark:write-to-file base.json + run: npm run benchmark:write-to-file --workspace=hyperformula base.json - name: (head) Checkout main repository uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # https://github.com/actions/checkout/releases/tag/v2.3.4 @@ -55,11 +55,11 @@ jobs: - name: (head) Run performance tests run: | - npm run benchmark:write-to-file head.json + npm run benchmark:write-to-file --workspace=hyperformula head.json - name: Compare the results run: | - npm run benchmark:compare-benchmarks base.json head.json performance-report.md + npm run benchmark:compare-benchmarks --workspace=hyperformula base.json head.json performance-report.md - name: Publish a comment - header uses: marocchino/sticky-pull-request-comment@6804b5ad49d19c10c9ae7cf5057352f7ff333f31 # https://github.com/marocchino/sticky-pull-request-comment/tree/v1.6.0 @@ -73,4 +73,4 @@ jobs: with: append: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - path: performance-report.md + path: hyperformula/performance-report.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c87e1f36d1..8eebea9a1a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: run: npm ci - name: Build docs - run: npm run docs:build + run: npm run docs:install && npm run docs:build - name: Deploy to GH pages uses: peaceiris/actions-gh-pages@ba0b7df03e25ff29c924be8149041119e9421ea6 # https://github.com/peaceiris/actions-gh-pages/releases/tag/v3.5.6 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9486ae9250..264d67847b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,10 +36,10 @@ jobs: with: ssh-key: ${{ secrets.DEPLOY_TOKEN }} repository: handsontable/hyperformula-tests - path: test/hyperformula-tests + path: hyperformula/test/hyperformula-tests - name: Fetch hyperformula-tests and sync branches - run: cd test && ./fetch-tests.sh + run: cd hyperformula/test && ./fetch-tests.sh - name: Install dependencies run: npm ci @@ -74,10 +74,10 @@ jobs: with: ssh-key: ${{ secrets.DEPLOY_TOKEN }} repository: handsontable/hyperformula-tests - path: test/hyperformula-tests + path: hyperformula/test/hyperformula-tests - name: Fetch hyperformula-tests and sync branches - run: cd test && ./fetch-tests.sh + run: cd hyperformula/test && ./fetch-tests.sh - name: Install dependencies run: npm ci diff --git a/.gitignore b/.gitignore index 886d7289b9..4409ab3db0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,32 @@ .idea/ .vscode -/commonjs/ -/coverage/ -/dist/ -/doc/ +/hyperformula/commonjs/ +/hyperformula/coverage/ +/hyperformula/dist/ +/hyperformula/doc/ /docs/api/ /docs/functions/ /docs/.vuepress/dist/ /docs/.vuepress/api-sidebar-relative.json /docs/.vuepress/api-sidebar.json -/typedoc/ -/es/ -/languages/ -/lib/ -/test-jasmine/ -/test-jest/ +/hyperformula/typedoc/ +/hyperformula/es/ +/hyperformula/languages/ +/hyperformula/lib/ +/hyperformula/test-jasmine/ +/hyperformula/test-jest/ node_modules/ -/typings/ -/storage/ +/hyperformula/typings/ +/hyperformula/storage/ +# Copied in by the package's prepack so the tarball carries one; the source is the +# CHANGELOG.md at the repository root. +/hyperformula/CHANGELOG.md *.iml dev*.html .DS_Store -/test/hyperformula-tests/ +/hyperformula/test/hyperformula-tests/ # Generated at docs:build from built-in-functions.tmpl.md (HF-249 single-source); do not commit. docs/guide/built-in-functions.md diff --git a/.worktreeinclude b/.worktreeinclude new file mode 100644 index 0000000000..74111d6626 --- /dev/null +++ b/.worktreeinclude @@ -0,0 +1,54 @@ +# Gitignored files copied into each worktree Claude Code creates. +# +# Uses .gitignore syntax. Only files that match a pattern AND are gitignored are +# copied, so nothing tracked is ever duplicated. Applies to worktrees created by +# `--worktree`, by subagent isolation, and by the desktop app. +# +# This covers only the file half of setting up a worktree. It cannot install +# dependencies and it cannot fetch the private test suite. See dev-docs/WORKTREES.md. + +# Nothing is copied into a worktree. The list below records what was considered +# and rejected, so it does not get added back. + +# Deliberately NOT copied — listing these here would cause real bugs: +# +# .dev.vars .dev.vars.* +# Cloudflare Worker deploy credentials. Copying them +# puts a deploy token on disk in every worktree, +# including the isolated ones subagents get, for the +# convenience of the one maintainer who actually runs +# `wrangler`. They can copy the file themselves. +# +# node_modules A copy is not an install: `.bin` shims and native +# builds do not survive it. Run `npm ci` in the worktree. +# Do NOT symlink it back to the main checkout: in a +# workspace `npm ci` then installs THROUGH the symlink +# and rewrites the main checkout's dependency tree. See +# dev-docs/WORKTREES.md. +# +# hyperformula/test/hyperformula-tests/ +# The private suite is BRANCH-MATCHED to this repository +# (test/fetch-tests.sh checks out the branch of the same +# name). A copy carries the source branch's tests and +# silently tests the wrong thing — worse than having no +# suite at all. Run `npm run test:setup-private` in the +# worktree instead. +# +# hyperformula/{lib,dist,es,commonjs,typings,languages}/ +# Build outputs. A stale copy makes `npm run test:browser` +# and the bundle checks pass or fail against the other +# branch's build. Run `npm run bundle-all`. +# +# docs/api/ docs/guide/built-in-functions.md docs/.vuepress/dist/ +# Generated from source. A copy goes stale the moment the +# JSDoc or the metadata catalogue changes. +# +# coverage/ test-jasmine/ test-jest/ .wrangler/ +# Run artifacts and local tool state. Nothing reads them +# across branches. +# +# .claude/settings.local.json +# Not needed. Since Claude Code v2.1.211 a permission +# approval made in a worktree is saved to the MAIN +# checkout's file and applies in every worktree of the +# repository, so a copy here would only go stale. diff --git a/AGENTS.md b/AGENTS.md index c81e681c12..84ab063818 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,14 @@ # AGENTS.md -Instructions for AI coding agents (Cursor, Claude Code, Codex, Aider, and any other AI tool) working in this repository. +Instructions for AI coding agents (Claude Code, Cursor, Codex, Aider, and any other AI tool) working in this repository. -## Start here +HyperFormula is a headless spreadsheet calculation engine in TypeScript. No UI, no DOM, no server: it parses formulas, tracks cell dependencies, and recalculates incrementally, in the browser and in Node. -Whatever you do, start by reading entire [DEV_DOCS.md](DEV_DOCS.md). Only then proceed to your task. +## Start at `dev-docs/` + +**[`dev-docs/`](dev-docs/) is the single source of truth for everything internal to this project.** Architecture, conventions, build, testing, standards, and the monorepo plan all live there and nowhere else. This file routes; it does not explain. Neither does any other `AGENTS.md` or `README.md`: each carries only what is so specific to its own directory that it would be useless anywhere else, and links for the rest. If one of them looks like it is explaining something general, the explanation belongs in `dev-docs/`. + +Read [`dev-docs/README.md`](dev-docs/README.md) first. It says which page covers what, and every directory's own `AGENTS.md` points at the page that covers that directory. ## Never publish sensitive information @@ -19,13 +23,9 @@ Describe the change on its own technical terms instead: write "fix an off-by-one If a change cannot be described without such information, stop and ask the user how to proceed. -## Other important resources +## Keep the documentation single-sourced -- the repository [README.md](README.md) — high-level project description and quick install/usage -- the markdown files in [`docs/guide/`](docs/guide/) — user-facing guides (installation, configuration, built-in functions, custom functions, integrations, etc.) -- the markdown files in [`docs/api/`](docs/api/) — API reference (generated from JSDoc; run `npm run docs:build` if the folder is missing) - -Prefer reading these local files over fetching the rendered documentation from the web. +When a change introduces a convention, constraint, file location, or gotcha that future agents should know, record it in `dev-docs/`, on the page that owns the topic — not in an `AGENTS.md`, not in a `README.md`, and not in a skill. Those three link to it. ## Response style @@ -44,14 +44,5 @@ This section is maintained by the team. Whenever an AI agent makes a mistake wor - **Short title** — What the agent did wrong. What it should have done instead. --> -1. Often pull request descriptions becomes obsolete. Remember to update it as you work. - -## Skills, MCPs, and other agent tools - -This section is maintained by the team. Skills, MCP servers, and other tools vetted as useful for AI agents working on this codebase are listed here. - - - -_No items yet._ +1. **Duplicating `dev-docs/`** — The agent explained a rule inside an `AGENTS.md`, a `README.md`, or a skill instead of linking to the `dev-docs/` page that owns it. Two copies of a rule means one of them is wrong within a release, and the reader cannot tell which. +2. **Stale pull request descriptions** — The description was written once and never revisited. Update it as the branch evolves. diff --git a/DEV_DOCS.md b/DEV_DOCS.md deleted file mode 100644 index 78fe3f49c0..0000000000 --- a/DEV_DOCS.md +++ /dev/null @@ -1,171 +0,0 @@ -# Developer documentation - -Canonical reference for everyone working on the HyperFormula source code: maintainers, the internal team, and AI agents triggered by them. Everything a developer needs to know lives here or is linked from here. - -## Quick links - -- **[Building, testing, and linting](docs/guide/building.md)** — all `npm` commands and build outputs -- **[Test suite](test/README.md)** — smoke tests and how to attach the private test suite -- **[Public docs portal](https://hyperformula.handsontable.com/docs)** — main documentation -- **[Docs README](docs/README.md)** — how to run the docs portal locally -- **[Docs content guide](DOCS_CONTENT_GUIDE.md)** — how to create and edit docs content -- **[Changelog](CHANGELOG.md)** -- **[Pull request template](.github/pull_request_template.md)** - -## Repository layout - -``` -. -├── src/ # Source code -│ ├── HyperFormula.ts # Main engine class, public API entry point -│ ├── parser/ # Formula parsing (uses Chevrotain parser generator) -│ ├── interpreter/ # Formula evaluation engine -│ │ └── plugin/ # Built-in spreadsheet function plugins -│ ├── DependencyGraph/ # Cell dependency tracking and recalculation order -│ ├── CrudOperations.ts # Create/read/update/delete operations on sheets and cells -│ └── i18n/ # Function-name translations per language -├── test/ # Test suite -├── docs/ # Public documentation portal (VuePress) -│ ├── guide/ # Markdown guides (building, contributing, usage…) -│ ├── api/ # API reference (generated from JSDoc) -│ ├── .vuepress/ # VuePress configuration, theme, components -│ └── README.md # How to run the docs portal locally -├── script/ # Maintenance and release scripts -├── .github/ # CI workflows, issue and PR templates -├── DEV_DOCS.md # Canonical developer documentation (this file) -├── AGENTS.md # Guidance for AI agents -├── CONTRIBUTING.md # Guide for external contributors -├── README.md # Project overview -├── CHANGELOG.md -├── LICENSE.txt -├── package.json -└── tsconfig.json -``` - -## Architecture - -### Core modules - -- `src/HyperFormula.ts` — main engine class, public API entry point -- `src/parser/` — formula parsing (uses the [Chevrotain](https://chevrotain.io/) parser generator) -- `src/interpreter/` — formula evaluation engine -- `src/DependencyGraph/` — cell dependency tracking and recalculation order -- `src/CrudOperations.ts` — create/read/update/delete operations on sheets and cells - -### Function plugins (`src/interpreter/plugin/`) - -All spreadsheet functions are implemented as plugins extending `FunctionPlugin`. Each plugin: - -- declares an `implementedFunctions` static property mapping function names to metadata -- uses the `runFunction()` helper for argument validation, coercion, and array handling -- registers function translations in `src/i18n/languages/` - -## Definition of Done - -Each change to the production code (bugfix, new feature, or improvement) must include the following elements **before** requesting a code review: - -- Changes to the production code - - including changes to all supported language packs in `src/i18n/languages` (if applicable) -- Automatic tests - - for bug fixes: at least one test reproducing the bug - - for new features: a set of tests precisely describing the feature - - pull requests from external contributors should include tests in the `test/` directory (they will be moved to the private repository by the internal team) - - the internal team adds tests directly to the private repository (through a separate pull request) -- Updates to documentation related to the change - - for breaking changes: a section in the migration guide -- Technical documentation in the form of JSDoc comments (high-level description of the concepts used in more complex code fragments) -- Changelog entry (not required for documentation-only changes (guides, JSDoc, README, etc.) -- Pull request description - -Every element of the change must not only be present but also correct: the changelog entry must describe the change accurately, and the documentation updates must match the new behaviour. - -Read through your own diff before requesting a review, and ask yourself what could be done better. Fix what you find while the change is still yours. - -A single pull request should contain an atomic self-contained functional change (single bugfix, single feature, single improvement). If a pull request contains multiple features or bugfixes, it should be split. Every change in the pull request must be relevant to the issue it solves — unrelated refactors, reformatting, or clean-ups belong in a separate pull request. - -## Code style - -- Prefer a functional approach where possible (`filter`, `map`, `reduce`). -- Write self-documenting code: use meaningful names for classes, functions, and variables. Add code comments only when they explain intent the code itself cannot. -- Add JSDoc to all classes and functions. -- Choose readability over brevity. Explicit, obvious code is better than a clever one-liner. -- Keep the logic straightforward: avoid convoluted control flow, prefer early returns and a flat structure, and make sure every branch and condition has a reason to exist. -- Follow clean code principles and general programming best practices: small functions with a single responsibility, no hidden side effects, no magic values. -- Avoid duplication. Extract shared logic instead of copying it, and reuse the existing helpers and abstractions of the codebase. -- Match the style of the surrounding code and of the project as a whole. New code should not stand out from its neighbours. -- Optimize for long-term maintainability: someone else should be able to read, extend, and safely change the code months from now. -- ESLint is the source of truth for formatting and code rules. Run `npm run lint` before submitting changes (see [building](docs/guide/building.md#run-the-linter)). - -## Performance - -HyperFormula is a calculation engine, so the performance of the production code is a feature, not an afterthought. - -- Consider the computational complexity of every change, especially in code that runs per cell, per formula, or per dependency-graph node. Nested loops over ranges and repeated work that could be computed once or cached are the usual suspects. -- Pick the best complexity that still keeps the code readable. When a faster algorithm is harder to follow, explain the trade-off in a JSDoc comment. -- Run `npm run test:performance` for changes that may affect the evaluation or CRUD hot paths. - -## Automatic tests - -- All changes to the production code (the `src/` directory) must be covered by automatic tests kept in the `test/` directory. -- Each test case must be very simple and focused on a single assertion. Don't use loops, conditionals, or other control flow statements in test cases. -- Cover more than the happy path: boundary values, empty and invalid input, error results, and interactions with related features. -- Before requesting a review, ask yourself which further tests would be valuable and add the ones that protect against realistic regressions. -- Don't add tests for code in the `docs/`, `examples/`, and `script/` directories. - -## Documentation - -- Follow the [documentation content guide](DOCS_CONTENT_GUIDE.md) when creating or editing docs (writing style, language, and how to structure guides). -- We try not to duplicate information in the documentation. The API reference (generated from JSDocs) should contain all the details about each function and class (it is the primary source of truth). Guides should provide high-level overview. They may duplicate some of the information from the API reference if they are relevant to the context but, above all, they should link to the API reference for the detailed information. - -## How to add a new function - -Adding a built-in function is similar to adding a [custom function](docs/guide/custom-functions.md), so that guide is a useful reference for the function-implementation patterns (argument metadata, return types, array handling). The built-in flow on top of that is: - -1. Create or modify a plugin in `src/interpreter/plugin/`. -2. Add function metadata to `implementedFunctions`. -3. Implement the function method. -4. Add a catalogue entry to `src/interpreter/functionMetadata/categories/.ts` (see below). -5. Add translations to all language files in `src/i18n/languages/`. -6. Add tests in `test/unit/interpreter/`. - -### The function metadata catalogue - -`src/interpreter/functionMetadata/categories/` holds the human-readable metadata for every built-in function: `shortDescription`, `parameters` (`snake_case` names, each with a description), `examples`, `documentationUrl`, and the category. It is the single source of truth for two consumers: the public [`getAvailableFunctions`/`getFunctionDetails`](docs/api/classes/hyperformula.md) API, and the generated built-in functions guide page (see [docs/README.md](docs/README.md)). - -Every field is required, `documentationUrl` included. Each entry authors its own link rather than inheriting a shared default, so that the links can diverge per function without touching any code; they all happen to point at the same guide page today. - -An entry's `category` must be one of the categories in `FUNCTION_CATEGORIES` — the ones the generated guide page renders as `### ` sections. The separate `'Custom'` category is reserved for user-registered functions and must never appear in `FUNCTION_CATEGORIES` or in a catalogue file: it names no section, and the docs generator rejects an entry carrying it rather than silently dropping it from the page it is building. (That rejection is also what turns a missing catalogue entry into a failed docs build. Arity drift below needs no such guard: the function still reaches the generator, which renders its degraded syntax line.) - -The catalogue's key set decides which ids carry an authored **description**, not which ids the API lists. Both `getAvailableFunctions` and `getFunctionDetails` describe every registered function — custom ones included — and an entry is applied whenever the catalogue holds one for the id, whichever plugin currently provides it: the catalogue is keyed by id, not by implementation, so a custom plugin registered over a built-in id is described with that built-in's authored metadata. Nothing checks a key against a registered function either, so an entry left behind after a rename describes nothing and merely ships in the bundle. Remove or rename it in the same change as the function. - -Two ways to get this wrong: - -- **No catalogue entry.** A registered function with no entry is still listed and still resolves to details, but as a custom function: `category: 'Custom'`, no `shortDescription`, `documentationUrl` or `examples` (the API omits every authored field it has no source for, rather than reporting an empty one), and positional parameter names (`Arg1`, `Arg2`, …). `'Custom'` has no section on the generated docs page, so `npm run docs:generate-function-docs` fails rather than publishing a built-in with no description. -- **Arity drift.** If the entry's parameter **count** disagrees with the plugin's `implementedFunctions`, the implementation wins: `getFunctionDetails` reports one parameter per implemented argument under positional names, discarding the authored names and descriptions, and warns on the console naming the function. The entry's category, `shortDescription`, `examples` and `documentationUrl` are still used, and the function stays listed — the parameter prose degrades, not the availability. - -Keep the entry's parameters in step with `implementedFunctions` whenever you change a signature. - -When a description **refers to** a parameter, use that parameter's exact `snake_case` name, never a prose variant: write "shifts `start_date` by …", not "shifts the start date by …". The same strings are rendered next to the generated syntax line, where the `snake_case` name is what the reader sees, so a prose variant leaves the reader guessing which argument is meant. This applies to `shortDescription` and to every parameter description. - -It does **not** turn ordinary English into identifiers. A parameter's own description may open with a prose noun phrase for the thing it describes — `lower_bound` is fine as "The lower bound, rounded up to an integer" — and words that merely happen to match a name ("entries that appear exactly once") stay as they are. The rule is about naming a *different* argument, or naming one from the syntax line. - -`shortDescription` must not use docs-page-local markup (no relative links, no footnote references): the strings are rendered by API consumers as well as by the docs page. - -Note what the drift warning does **not** cover: **optionality is not cross-checked.** The catalogue authors no optionality of its own — a parameter's `optional` flag is derived entirely from `optionalArg`/`defaultValue` in `implementedFunctions` — so a description that calls an argument optional can sit next to `optional: false` with nothing failing. When a function accepts a call that arity alone does not express (`SHEET()`, `ROW()`, and anything else served by `runFunctionWithReferenceArgument`'s zero-argument path), the plugin must declare `optionalArg: true` explicitly, or the public API will advertise the argument as required. `ROW`, `COLUMN`, `SHEET` and `SHEETS` all declare it; `ISFORMULA` takes the same path and correctly does not, because its zero-argument call is an error rather than a shorthand. - -Descriptions must describe **HyperFormula's** behaviour, not Excel's. Much of the catalogue was seeded from a hand-written page that documented Excel, and HyperFormula deliberately deviates in places (`INT` truncates toward zero, `ISEVEN`/`ISODD` do not truncate, `CEILING.MATH`/`FLOOR.MATH` honour only `mode` = 1). Verify a claim against the implementation before authoring it, and record any deviation in [the list of differences](docs/guide/list-of-differences.md). - -## Internationalization and function translations - -HyperFormula supports internationalization and provides localized function names for all built-in languages. Translation files live in `src/i18n/languages/`. New functions must include translations for all built-in languages. - -When looking for the valid translations for new functions, try these sources: - -- https://support.microsoft.com/en-us/office/excel-functions-translator-f262d0c0-991c-485b-89b6-32cc8d326889 -- http://dolf.trieschnigg.nl/excel/index.php - -For languages not officially supported by Microsoft Excel, the two sources above do not apply. For these languages, use Google Sheets as the reference. Switch the `hl` query parameter to the target locale, for example: - -- https://support.google.com/docs/table/25273?hl=id (Indonesian) - -For functions that Google Sheets does not list either, fall back to the English name (matching the convention used by Excel in unsupported locales). diff --git a/README.md b/README.md index 8d3d14707b..36b140f888 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,41 @@ -
-

- - HyperFormula - A headless spreadsheet, a parser and evaluator of Excel formulas - -

+# HyperFormula monorepo -

- An open-source headless spreadsheet for business web apps -

+[HyperFormula](https://hyperformula.handsontable.com/) is a headless spreadsheet calculation engine in TypeScript. It parses formulas, tracks cell dependencies, and recalculates incrementally, in the browser and in Node. -

- npm total downloads - npm monthly downloads - GitHub contributors - Known Vulnerabilities -
- FOSSA Status - GitHub Workflow Status - codecov -

+This repository holds the engine and everything built around it. ---- +| Directory | What it is | Published | +|---|---|---| +| [`hyperformula/`](hyperformula/) | The calculation engine. **Start here** — its [README](hyperformula/README.md) is the product documentation. | yes | +| [`hyperformula-ui/`](hyperformula-ui/) | UI components for working with HyperFormula. Not imported yet. | yes | +| [`docs/`](docs/) | The documentation portal. Installed separately; not a workspace member. | no | -HyperFormula is a headless spreadsheet built in TypeScript, serving as both a parser and evaluator of spreadsheet formulas. It can be integrated into your browser or utilized as a service with Node.js as your back-end technology. +The published packages release together, on one version, and share the single [`CHANGELOG.md`](CHANGELOG.md) at the root. -## What HyperFormula can be used for? - -HyperFormula doesn't assume any existing user interface, making it a general-purpose library that can be used in various business applications. Here are some examples: - -- Deterministic compute layer for AI & LLMs -- Calculated fields in CRM and ERP software -- Custom spreadsheet-like app -- Business logic builder -- Forms and form builder -- Educational app -- Online calculator - -## Features - -- [Function syntax compatible with Microsoft Excel](https://hyperformula.handsontable.com/docs/guide/compatibility-with-microsoft-excel.html) and [Google Sheets](https://hyperformula.handsontable.com/docs/guide/compatibility-with-google-sheets.html) -- High-speed parsing and evaluation of spreadsheet formulas -- [A library of ~400 built-in functions](https://hyperformula.handsontable.com/docs/guide/built-in-functions.html) -- [Support for custom functions](https://hyperformula.handsontable.com/docs/guide/custom-functions.html) -- [Support for Node.js](https://hyperformula.handsontable.com/docs/guide/server-side-installation.html#install-with-npm-or-yarn) -- [Support for undo/redo](https://hyperformula.handsontable.com/docs/guide/undo-redo.html) -- [Support for CRUD operations](https://hyperformula.handsontable.com/docs/guide/basic-operations.html) -- [Support for clipboard](https://hyperformula.handsontable.com/docs/guide/clipboard-operations.html) -- [Support for named expressions](https://hyperformula.handsontable.com/docs/guide/named-expressions.html) -- [Support for data sorting](https://hyperformula.handsontable.com/docs/guide/sorting-data.html) -- [Support for formula localization with 17 built-in languages](https://hyperformula.handsontable.com/docs/guide/i18n-features.html) -- Easy integration with any front-end or back-end application -- GPLv3 or a [commercial license](https://handsontable.com/get-a-quote) -- Maintained by the team that stands behind the [Handsontable](https://handsontable.com/) data grid - -## Documentation - -- [Client-side installation](https://hyperformula.handsontable.com/docs/guide/client-side-installation.html) -- [Server-side installation](https://hyperformula.handsontable.com/docs/guide/server-side-installation.html) -- [Basic usage](https://hyperformula.handsontable.com/docs/guide/basic-usage.html) -- [Configuration options](https://hyperformula.handsontable.com/docs/guide/configuration-options.html) -- [List of built-in functions](https://hyperformula.handsontable.com/docs/guide/built-in-functions.html) -- [API Reference](https://hyperformula.handsontable.com/docs/api/) - -## Integrations - -- [Integration with React](https://hyperformula.handsontable.com/docs/guide/integration-with-react.html#demo) -- [Integration with Angular](https://hyperformula.handsontable.com/docs/guide/integration-with-angular.html#demo) -- [Integration with Vue](https://hyperformula.handsontable.com/docs/guide/integration-with-vue.html#demo) -- [Integration with Svelte](https://hyperformula.handsontable.com/docs/guide/integration-with-svelte.html#demo) - -## Installation and usage - -Install the library from [npm](https://www.npmjs.com/package/hyperformula) like so: +## Getting started ```bash -npm install hyperformula +npm ci # installs the workspace +npm run test:setup-private # attaches the private test suite, if you have access +npm run test:jest # the fast test loop +npm run bundle-all # every bundle for the engine ``` -Once installed, you can use it to develop applications tailored to your specific business needs. Here, we've used it to craft a form that calculates mortgage payments using the `PMT` formula. - -```js -import { HyperFormula } from 'hyperformula'; - -// Create a HyperFormula instance -const hf = HyperFormula.buildEmpty({ licenseKey: 'gpl-v3' }); - -// Add an empty sheet -const sheetName = hf.addSheet('Mortgage Calculator'); -const sheetId = hf.getSheetId(sheetName); - -// Enter the mortgage parameters -hf.addNamedExpression('AnnualInterestRate', '8%'); -hf.addNamedExpression('NumberOfMonths', 360); -hf.addNamedExpression('LoanAmount', 800000); +Root scripts fan out to the packages; run a package's own scripts from its directory, or with `--workspace=hyperformula`. -// Use the PMT function to calculate the monthly payment -hf.setCellContents({ sheet: sheetId, row: 0, col: 0 }, [['Monthly Payment', '=PMT(AnnualInterestRate/12, NumberOfMonths, -LoanAmount)']]); +The documentation portal installs on its own: -// Display the result -console.log(`${hf.getCellValue({ sheet: sheetId, row: 0, col: 0 })}: ${hf.getCellValue({ sheet: sheetId, row: 0, col: 1 })}`); +```bash +npm run docs:install +npm run docs:dev ``` -[Run this code in StackBlitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.4.x/mortgage-calculator) - -HyperFormula ships an official Claude skill and machine-readable docs, so your AI coding agent can scaffold, configure, and debug HyperFormula correctly. To install the skill in Claude Code, or to point Cursor, GitHub Copilot, or another agent at the docs, see [Set up your coding agent](https://hyperformula.handsontable.com/docs/guide/setup-coding-agent.html). - -## Contributing - -Contributions are welcome, but before you make them, please read the [Contributing Guide](https://hyperformula.handsontable.com/docs/guide/contributing.html) and accept the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). +## Contributing and development -## License +- External contributors: [`CONTRIBUTING.md`](CONTRIBUTING.md) +- Everyone working on the source, including AI agents: [`dev-docs/README.md`](dev-docs/README.md) — architecture, build, testing, standards, and the definition of done +- AI coding agents: [`AGENTS.md`](AGENTS.md) -HyperFormula is available under two different licenses: GPLv3 and proprietary. The proprietary license can be purchased by [contacting our team](https://handsontable.com/get-a-quote) at Handsontable. +## Licence -Copyright (c) Handsoncode +GPL-3.0-only, plus a commercial licence. See [`LICENSE.txt`](LICENSE.txt). diff --git a/dev-docs/AGENT-TOOLING.md b/dev-docs/AGENT-TOOLING.md new file mode 100644 index 0000000000..3cd9f7155d --- /dev/null +++ b/dev-docs/AGENT-TOOLING.md @@ -0,0 +1,54 @@ +# Agent tooling + +How this repository is configured for AI coding agents. The rules an agent must follow are elsewhere — this page is about the machinery. + +## The three layers + +| Layer | Answers | Loaded | Rule | +|---|---|---|---| +| `AGENTS.md` | *What is this directory, and where do I look next?* | Always, within its subtree | A pointer of a few lines. Never a place to explain anything. | +| `dev-docs/` | *How does this work and why?* | On demand | **The single source of truth for internal knowledge.** | +| `.claude/skills/` | *How do I do task X?* | On skill trigger | Steps and ordering. Links to `dev-docs/` for the rules. | + +`CLAUDE.md` is a symlink to the sibling `AGENTS.md` in every directory that has one, so Claude Code and Cursor read the same file. + +**Nothing outside `dev-docs/` restates what is in `dev-docs/`.** An `AGENTS.md`, a `README.md`, or a `SKILL.md` carries only what is so specific to its own context that it would be useless anywhere else; everything else is a link. Two copies of a rule means one of them is wrong within a release, and the reader cannot tell which. + +## `.claude/settings.json` + +Committed, so every developer gets the same setup. + +| Key | Why | +|---|---| +| `enabledPlugins` | `typescript-lsp` — language-server go-to-definition and find-references. Use it instead of grepping for a symbol's definition or callers; grep stays right for text searches. | +| `permissions.deny` | Blocks agent reads of **build artifacts**. They are git-ignored, so content searches already skip them, but nothing otherwise stops an agent opening `dist/hyperformula.js` or answering a behaviour question from `typings/` instead of `hyperformula/src/`. | + +**Generated documentation is not a build artifact, and is deliberately readable.** `docs/api/` and `docs/guide/built-in-functions.md` are produced by a build step, but they are the API reference and the function reference — reading them to answer a question is the right move, and `FUNCTION-CATALOGUE.md` links straight into them. The rule is about *artifacts*: bundles, declarations, coverage, and the compiled site. Editing either of those files is still always wrong; that is what the build regenerates. + +`node_modules/` and `package-lock.json` are deliberately readable too: reading a dependency's source is sometimes the right move when debugging, and a deny rule would also block a targeted grep for a dependency version. + +Relative deny patterns anchor at the session's working directory, and project settings are not inherited from parent directories — these rules apply to sessions started at the repository root. + +## Read the repository, not the web + +The guides in `docs/guide/` and the generated API reference in `docs/api/` are the same content the documentation portal serves. Read the local files rather than fetching the rendered pages, and read `hyperformula/src/` rather than either when the question is what the code actually does. + +## Skills + +All skills live in `.claude/skills/`, at the repository root — one directory to look in, one to keep consistent. A skill that belongs to part of the tree is scoped by the `paths` frontmatter field (one glob, or a comma-separated list) rather than by placement. A skill that applies anywhere — writing a changelog entry, opening a pull request, test discipline, reviewing a diff — carries no `paths` and is chosen from its description alone. + +A skill holds the **steps**: what to do, in what order, and what to check. It does not restate the rules those steps enforce — it links to the `dev-docs/` page that owns them. + +**Step 1 of every skill is "Read the relevant files from `dev-docs/`", and it names them.** Not a general pointer at the directory: a table of the specific pages, each with one line saying why that page matters for this task. Where a task's reference depends on what it touches, the step lists the always-read pages first and then the conditional ones. A skill whose first step is anything else is missing it. + +| Skill | For | +|---|---| +| `hyperformula-dev` | Any work in `hyperformula/src/` — the entry point | +| `hyperformula-function-dev` | Adding or changing a built-in function | +| `hyperformula-unit-testing` | Writing or modifying tests | +| `test-writing-discipline` | Any red test, and any test that might be going green for the wrong reason | +| `i18n-translations` | Function-name translations | +| `writing-docs-pages` | The documentation portal | +| `changelog-creation` | The changelog entry | +| `pr-creation` | Opening or updating a pull request | +| `hyperformula-code-review` | Reviewing a diff, a branch, or a pull request | diff --git a/dev-docs/BUILD.md b/dev-docs/BUILD.md new file mode 100644 index 0000000000..9aa3de266a --- /dev/null +++ b/dev-docs/BUILD.md @@ -0,0 +1,45 @@ +# Building and releasing + +How the workspace installs, and the repository-level steps: generating the documentation, deploying the portal, and cutting a release. + +The engine's own build — the intermediate `lib/`, the bundles, and packaging — is in [`hyperformula/dev-docs/BUILD.md`](../hyperformula/dev-docs/BUILD.md). + +## Install + +Node version is pinned in [`.nvmrc`](../.nvmrc) — 22, the same in every package. The repository uses npm workspaces with a committed `package-lock.json`, so install with `npm ci`, not `npm install`. + +```bash +npm ci # the workspace: hyperformula, hyperformula-ui +npm run docs:install # the portal, which is not a workspace member +``` + +Root scripts fan out to the packages. `npm run lint` is the exception: it runs once, from the root, over the whole repository. To run a package's own scripts, work from its directory or pass `--workspace=hyperformula`. + +## Generated documentation + +| File | Generated by | Rule | +|---|---|---| +| `docs/api/` | `npm run typedoc:build-api` — TypeDoc runs inside `hyperformula/`, where the `tsconfig.json` is, and writes across into the portal | Never edit. Change the JSDoc in `hyperformula/src/`. | +| `docs/guide/built-in-functions.md` | `npm run docs:generate-function-docs`, which runs `docs/script/generate-builtin-functions-doc.ts` against the metadata catalogue | Never edit, never commit. Needs the portal installed. | + +The generator lives in `docs/script/` rather than beside the engine build scripts because it uses `@vuepress/shared-utils` for slugs, and that package only resolves inside a full VuePress dependency tree. + +Both are git-ignored, and both are regenerated as the first step of `docs:dev` and `docs:build`. `docs:generate-function-docs` is a gate, not a formatter: it fails the build on a missing catalogue entry or a `'Custom'` category. See [`FUNCTION-CATALOGUE.md`](../hyperformula/dev-docs/FUNCTION-CATALOGUE.md). + +## Deploying the portal + +The portal is served by a Cloudflare Worker, and **deployment is driven by Workers Builds on push, not from a developer's machine**: `master` deploys to production, every other branch uploads a preview version. The commands below exist for debugging that pipeline. The triggers, the Worker name, and the dashboard build settings are in [`docs/README.md`](../docs/README.md#deployment). + +| Command | Does | +|---|---| +| `npm run docs:build:cf` | Full portal build, then composes the Worker asset tree through `docs/script/prepare-cf-assets.js` | +| `npm run docs:deploy:cf` | `wrangler deploy` — production | +| `npm run docs:preview:cf` | `wrangler versions upload` — a per-branch preview URL | + +Config is [`docs/wrangler.jsonc`](../docs/wrangler.jsonc); the Worker entry point is [`docs/worker/index.js`](../docs/worker/index.js). + +## Release and licences + +`npm run release` runs `script/release/release.sh`. Releasing is maintainer-owned — do not invent steps around it, and do not run it as part of another task. + +`npm run check:licenses` asserts that every production dependency carries a permissive licence. diff --git a/dev-docs/CODE-STYLE.md b/dev-docs/CODE-STYLE.md new file mode 100644 index 0000000000..e4468886ae --- /dev/null +++ b/dev-docs/CODE-STYLE.md @@ -0,0 +1,24 @@ +# Code style + +ESLint is the source of truth for formatting and code rules — run `npm run lint` before submitting changes (see [the linter section of the building guide](../docs/guide/building.md#run-the-linter)). Some of what follows it enforces, the `jsdoc` rules among them; the rest is what no linter can check for you. + +## Style + +- Prefer a functional approach where possible (`filter`, `map`, `reduce`). +- Write self-documenting code: meaningful names for classes, functions, and variables. Add comments only where they explain intent the code itself cannot. +- Add JSDoc to all classes and functions. +- Choose readability over brevity. Explicit, obvious code beats a clever one-liner. +- Keep control flow straightforward: early returns, flat structure, and a reason for every branch and condition. +- Small functions with a single responsibility. No hidden side effects, no magic values. +- Avoid duplication. Extract shared logic instead of copying it, and reuse the codebase's existing helpers and abstractions. +- Match the style of the surrounding code and of the project as a whole. New code should not stand out from its neighbours. +- Optimize for long-term maintainability: someone else should be able to read, extend, and safely change the code months from now. + +## TypeScript + +- The public API surface is `hyperformula/src/HyperFormula.ts` and the types re-exported from `hyperformula/src/index.ts`; `npm run bundle:typings --workspace=hyperformula` emits them into `hyperformula/typings/`. +- `npm run verify:typings` (`tsc --noEmit`) must pass. A change that only compiles because of an `as` cast usually has a modelling problem behind it. + +## Performance + +Where a package's code is on a hot path, its own reference says so and names the paths. For the engine — where performance is a feature rather than an afterthought — that is [`hyperformula/dev-docs/PERFORMANCE.md`](../hyperformula/dev-docs/PERFORMANCE.md). diff --git a/dev-docs/DEFINITION-OF-DONE.md b/dev-docs/DEFINITION-OF-DONE.md new file mode 100644 index 0000000000..253b00855e --- /dev/null +++ b/dev-docs/DEFINITION-OF-DONE.md @@ -0,0 +1,34 @@ +# Definition of done + +Every change to production code — bug fix, feature, or improvement — must include all of the following **before** a code review is requested. + +1. **The production change**, including every supported language pack in `hyperformula/src/i18n/languages/` when function names are involved. +2. **Automatic tests** in `hyperformula/test/`: + - bug fix — at least one test that reproduces the bug; + - new feature — a set of tests that precisely describe the feature; + - pull requests from external contributors put tests in `hyperformula/test/`; the internal team adds them to the private repository through a separate pull request. + See [`TESTING.md`](TESTING.md). +3. **Documentation updates** matching the change. A breaking change also needs a section in the migration guide. See [`DOC-STANDARDS.md`](DOC-STANDARDS.md). +4. **JSDoc** on classes and functions, plus a high-level description of the concepts used in any complex fragment. +5. **A changelog entry**. Not needed for documentation-only, test-only, or CI and tooling changes, nor for a bug that was introduced and never released. The full rule, and how to write the entry, is in [`DOC-STANDARDS.md`](DOC-STANDARDS.md#the-changelog). +6. **A pull request description** — kept current as the branch evolves, not written once and left to rot. + +Every element must be not only present but correct: the changelog entry must describe the change accurately, and the documentation must match the new behaviour. + +## Before requesting a review + +Read your own diff end to end and ask what could be done better. Fix what you find while the change is still yours. + +## One pull request, one change + +A pull request contains a single atomic, self-contained functional change: one bug fix, one feature, or one improvement. A pull request with several of those should be split. + +Every change in the pull request must be relevant to the issue it solves. Unrelated refactors, reformatting, and clean-ups belong in a separate pull request — including the ones that are obviously improvements. + +## Breaking changes + +The public API is `hyperformula/src/HyperFormula.ts` and the types re-exported from `hyperformula/src/index.ts`. Avoid breaking it. When a change genuinely requires a break: + +- state it explicitly in the pull request description and the changelog entry; +- add a migration-guide section describing what breaks and what to do instead; +- keep a test proving the old behaviour where the old behaviour is meant to keep working. diff --git a/dev-docs/DOC-STANDARDS.md b/dev-docs/DOC-STANDARDS.md new file mode 100644 index 0000000000..696df39319 --- /dev/null +++ b/dev-docs/DOC-STANDARDS.md @@ -0,0 +1,68 @@ +# Documentation standards + +These apply across the whole repository: guides, the API reference, JSDoc inside `hyperformula/src/`, the changelog, migration guides, and README files. + +*How* to write a page — structure, chunking, language, code examples, and the self-review checklist — is [`DOCS-CONTENT-GUIDE.md`](DOCS-CONTENT-GUIDE.md). This page is *when* documentation is required and what it must be true about. + +## When documentation is required + +- Any public-API change updates the JSDoc **and** the affected guides. +- Any user-facing behaviour change is documented in the same pull request as the change. +- Any breaking change adds a migration-guide section. +- Documentation-only changes need no changelog entry. The full exemption is under [The changelog](#the-changelog). + +## The API reference is the source of truth + +The API reference, generated from JSDoc, holds all the detail about each function and class. A guide gives the overview and links to the reference for the detail. + +This is not a licence to make a guide unreadable on its own. A guide page repeats the small essential context a reader needs to finish the task in front of them, and links out only for depth. [`DOCS-CONTENT-GUIDE.md`](DOCS-CONTENT-GUIDE.md#how-to-structure-a-page) owns that trade-off and states the rule of thumb: if removing a link would make the current task impossible to finish, that information belongs on the page. + +## Generated files + +| File | Generated by | Rule | +|---|---|---| +| `docs/api/` | `npm run typedoc:build-api` | Never edit. Change the JSDoc in `hyperformula/src/`. | +| `docs/guide/built-in-functions.md` | `npm run docs:generate-function-docs` | Never edit, never commit. Change the metadata catalogue — see [`FUNCTION-CATALOGUE.md`](../hyperformula/dev-docs/FUNCTION-CATALOGUE.md). | + +Both are git-ignored. A missing `docs/api/` folder means it has not been built yet; run `npm run typedoc:build-api`. + +## Describing behaviour + +Describe **HyperFormula's** behaviour, not Excel's. HyperFormula deliberately deviates in places, and much of the existing prose was seeded from Excel documentation. Verify a claim against the implementation before writing it down, and record any deviation in [the list of differences](../docs/guide/list-of-differences.md). + +## The changelog + +`CHANGELOG.md` at the repository root is the single history for every package — they release together, on one version. It follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/); the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries go under `## [Unreleased]`, in the section matching the change — create the `### ` heading if it is not there yet. Name the package an entry concerns when it is not obvious from the text. + +| Section | For | +|---|---| +| `Added` | A wholly new capability — a function, an option, a language pack, an API method | +| `Changed` | Modified behaviour of something that already existed | +| `Fixed` | A bug fix | +| `Deprecated` | Scheduled for removal | +| `Removed` | Already removed in this release | +| `Security` | A vulnerability fix | + +One bullet per change, ending with a link to the public issue where one exists, otherwise to the pull request: + +```markdown +- Fixed the `MOD` function returning a remainder with the sign of the dividend instead of the sign of the divisor, which made the results differ from Excel and Google Sheets for arguments with opposite signs (e.g. `=MOD(-3, 12)` now returns `9` instead of `-3`). [#1747](https://github.com/handsontable/hyperformula/issues/1747) +``` + +Writing the entry: + +- **From the user's perspective.** What changed for someone using HyperFormula, not what changed in the code. No class names, no file paths. +- **Past tense, verb first**: "Added…", "Fixed…", "Changed…", "Removed…". +- **Specific.** Name the function, option, or operation, and say what it does now. Show the difference when a value changed. +- **Nothing sensitive.** No client, customer, or partner names, and nothing that identifies them indirectly. +- End with a period, then the link. + +A breaking change says what breaks and what to do instead, and still needs a migration-guide section — the entry is not a substitute. + +**No entry is needed** for documentation-only, test-only, or CI and tooling changes, or for a bug that was introduced and never released. + +## The documentation portal + +`docs/` is a VuePress site. How to run it, what is generated, and how to add a page are in [`docs/README.md`](../docs/README.md). + +Two rules from above bear repeating there, because they are the ones most often broken while writing a guide: do not restate the API reference, and describe HyperFormula rather than Excel. diff --git a/DOCS_CONTENT_GUIDE.md b/dev-docs/DOCS-CONTENT-GUIDE.md similarity index 97% rename from DOCS_CONTENT_GUIDE.md rename to dev-docs/DOCS-CONTENT-GUIDE.md index 08d160f128..badbac09cb 100644 --- a/DOCS_CONTENT_GUIDE.md +++ b/dev-docs/DOCS-CONTENT-GUIDE.md @@ -16,8 +16,6 @@ helps humans and search. You are not gaming an algorithm — you are writing cle --- - - ## Project context - **Product:** HyperFormula — an open-source, headless spreadsheet and formula @@ -36,8 +34,6 @@ linked per version branch (e.g. `3.3.x`). --- - - ## What to write (coverage & gap prioritization) Optimization cannot recover a page that doesn't exist. Coverage comes first. @@ -69,8 +65,6 @@ Optimization cannot recover a page that doesn't exist. Coverage comes first. --- - - ## How to structure a page Treat **every page as page one.** @@ -102,8 +96,6 @@ Treat **every page as page one.** --- - - ## How to structure sections (chunking) 1. **One purpose per section.** Each `##`/`###` answers exactly one question. Don't @@ -132,8 +124,6 @@ Treat **every page as page one.** --- - - ## Language & terminology 1. **Write plainly.** Aim for a ~6th–7th-grade reading level: short sentences, one @@ -157,8 +147,6 @@ Treat **every page as page one.** --- - - ## Code examples Code is the primary content of these docs. Models reproduce complete examples well @@ -184,8 +172,6 @@ and hallucinate the parts you omit. --- - - ## Visuals, tables & the "why" 1. **Never put information only in an image, diagram, or video.** Assistants can't @@ -209,8 +195,6 @@ and hallucinate the parts you omit. --- - - ## VuePress conventions (so your output fits the site) - Start each page with frontmatter: @@ -237,33 +221,18 @@ VuePress — don't hand-write navigation. --- - - - ## Self-review checklist (run before finishing any page) -- [ ] **Stands alone:** a reader who lands here cold, seeing only this page, can - ``` - finish the task without opening another page. - ``` +- [ ] **Stands alone:** a reader who lands here cold, seeing only this page, can finish the task without opening another page. - [ ] **One question per section**, descriptive headings, hierarchy in order. -- [ ] **No walls of text:** long prose is broken into granular H2/H3 subsections, - ``` - each short enough that its heading covers everything under it. - ``` +- [ ] **No walls of text:** long prose is broken into granular H2/H3 subsections, each short enough that its heading covers everything under it. - [ ] **Product named** in the body; no bare "the library/the method/the grid." - [ ] **No backward references** ("as above," "now that you've…"). - [ ] **Prerequisites stated explicitly**; nothing assumed. - [ ] **Terminology consistent** with the canonical terms; acronyms expanded once. -- [ ] **Every code block** is language-tagged, complete, includes the `licenseKey`, - ``` - and would actually run; cell-address property order is `{ sheet, row, col }`. - ``` +- [ ] **Every code block** is language-tagged, complete, includes the `licenseKey`, and would actually run; cell-address property order is `{ sheet, row, col }`. - [ ] **No info trapped in images/tables**; visuals have text equivalents and captions. - [ ] **Frontmatter** has a specific `title` and a one-sentence `description`. - [ ] **No contradiction** with other pages; old facts updated everywhere. - [ ] **"Why" is covered:** intent, when-to-use, and known gotchas — not just syntax. -- [ ] Would an AI assistant quoting *only this page* give a correct, complete answer? - ``` - If not, fix the page. - ``` +- [ ] Would an AI assistant quoting *only this page* give a correct, complete answer? If not, fix the page. diff --git a/dev-docs/PULL-REQUESTS.md b/dev-docs/PULL-REQUESTS.md new file mode 100644 index 0000000000..c32207eaa3 --- /dev/null +++ b/dev-docs/PULL-REQUESTS.md @@ -0,0 +1,56 @@ +# Pull requests + +## Branch naming + +`/-`, lowercase, hyphen-separated: + +``` +feat/hf-305-overwrite-flag +fix/hf-357-mod-divisor-sign +docs/hf-282-counts-guide +spike/hf-270-null-to-zero +``` + +Never put a client name, a customer report's wording, or anything from a private ticket in a branch name. The ticket identifier alone is fine. + +Never force-push to `master`, `develop`, or a branch that already has an open pull request. + +## Order of operations + +1. Commit the source change on a feature branch. +2. Run the pre-flight gate below. +3. Push, and open the pull request. +4. Add the changelog entry, linking that pull request or the public issue it fixes — see [`DOC-STANDARDS.md`](DOC-STANDARDS.md#the-changelog). +5. Commit and push the entry to the same branch. + +The entry comes after the pull request exists, because it carries the link. Do not guess the number. + +## Pre-flight gate + +```bash +npm run test:setup-private # after any branch switch +npm run lint +npm run test:jest +``` + +Add `npm run test` (which includes the browser run) when the change touches bundling, module format, or anything browser-specific. Add `npm run docs:generate-function-docs` when it touches a function or its catalogue entry. + +Read the output. Do not open a pull request on a red run and describe it as ready. + +## The template + +[`.github/pull_request_template.md`](../.github/pull_request_template.md) is filled in, not deleted. + +- **Context** — why the change is needed, written for a reviewer who has not seen the ticket. +- **How did you test your changes?** — the commands actually run and what they showed, not "added tests". +- **Types of changes** — tick every box that applies, breaking change included, honestly. +- **Related issues** — `Fixes #...` for a public issue. A private ticket identifier may be named; its contents may not. +- **Checklist** — the OpenDocument, Excel, and Google Sheets boxes are real questions about the change. If the behaviour deliberately deviates, say so in Context and record it in [`docs/guide/list-of-differences.md`](../docs/guide/list-of-differences.md). + +## Scope and upkeep + +One pull request holds one atomic, self-contained change. Unrelated refactors, reformatting, and clean-ups belong in their own — including the ones that are obviously improvements. + +**Keep the description current.** A description written once and never revisited is the most common failure in this repository. When the branch changes scope, update it in the same push. + +Read your own diff end to end before asking anyone else to. Everything a change must contain is in [`DEFINITION-OF-DONE.md`](DEFINITION-OF-DONE.md). diff --git a/dev-docs/README.md b/dev-docs/README.md new file mode 100644 index 0000000000..22196d9771 --- /dev/null +++ b/dev-docs/README.md @@ -0,0 +1,52 @@ +# Developer documentation + +The canonical reference for everyone working on the HyperFormula source: maintainers, the internal team, and the AI agents they run. Written for humans first; agents read the same files. + +Everything a developer needs to know lives here or is linked from here. External contributors start with [`CONTRIBUTING.md`](../CONTRIBUTING.md); users start with the [documentation portal](https://hyperformula.handsontable.com/docs). + +## How the documentation is organised + +| Layer | Answers | Loaded | +|---|---|---| +| `AGENTS.md` | *What is this directory, and where do I look next?* A pointer, nothing more. | Always, within its subtree | +| `dev-docs/` | *How does this work and why?* | On demand | +| `.claude/skills/` | *How do I do task X?* | On skill trigger | + +In every directory, `CLAUDE.md` is a symlink to its sibling `AGENTS.md`. Edit `AGENTS.md` — the symlink keeps Claude Code and Cursor reading the same single source. + +`dev-docs/` exists at two levels. This one holds what applies to the whole repository; each package holds its own internals. A fact has exactly one home in exactly one of them — the split is by ownership, never a copy. + +## Where to look + +Repository-wide standards and process live here. Each package documents its own internals: the engine's are in [`hyperformula/dev-docs/`](../hyperformula/dev-docs/README.md). + +| You are working on | Read | +|---|---| +| What a change must include before review | [`DEFINITION-OF-DONE.md`](DEFINITION-OF-DONE.md) | +| Code style | [`CODE-STYLE.md`](CODE-STYLE.md) | +| What a test must prove, and how a case is written | [`TESTING.md`](TESTING.md) | +| Documentation rules, and the changelog | [`DOC-STANDARDS.md`](DOC-STANDARDS.md) | +| Writing a documentation page | [`DOCS-CONTENT-GUIDE.md`](DOCS-CONTENT-GUIDE.md) | +| Installing the workspace, deploying the portal, cutting a release | [`BUILD.md`](BUILD.md) | +| Opening a pull request | [`PULL-REQUESTS.md`](PULL-REQUESTS.md) | +| Which package holds what, and what the monorepo move still owes | [`STRUCTURE.md`](STRUCTURE.md) | +| A linked git worktree | [`WORKTREES.md`](WORKTREES.md) | +| How this repository is set up for agents | [`AGENT-TOOLING.md`](AGENT-TOOLING.md) | +| Step-by-step task workflows | [`.claude/skills/`](../.claude/skills/) | + +Inside the engine — architecture, the parser, the interpreter, the dependency graph, the function catalogue, translations, performance, its test suites and its build: + +| You are working on | Read | +|---|---| +| Anything in `hyperformula/src/` | [`hyperformula/dev-docs/README.md`](../hyperformula/dev-docs/README.md) | + +Outside both: [`docs/README.md`](../docs/README.md) for running the documentation portal, [`hyperformula/test/README.md`](../hyperformula/test/README.md) for attaching the private test suite, and [`CONTRIBUTING.md`](../CONTRIBUTING.md) for external contributors. + +## Conventions + +- Markdown **link targets** are filesystem-relative — `../hyperformula/test/README.md` from here, `../../../dev-docs/TESTING.md` from a nested `AGENTS.md` — so they resolve on GitHub and in an editor. +- A path **named in prose** rather than linked is spelled from the repository root: `dev-docs/TESTING.md`, `hyperformula/src/interpreter/plugin/`. +- Diagrams live only in `dev-docs/`, never in the always-loaded `AGENTS.md` files. +- Public, user-facing documentation belongs in [`docs/`](../docs/), not here. `dev-docs/` never ships. +- **Nothing outside this directory restates what is in it.** `AGENTS.md`, `README.md`, and `SKILL.md` files carry only what is so specific to their own context that it would be useless anywhere else; everything else is a link. Two copies of a rule means one of them is wrong within a release, and the reader cannot tell which. +- `.ai/` exists only because some agents look for it. It contains one sentence pointing here. diff --git a/dev-docs/STRUCTURE.md b/dev-docs/STRUCTURE.md new file mode 100644 index 0000000000..b6b899dffb --- /dev/null +++ b/dev-docs/STRUCTURE.md @@ -0,0 +1,126 @@ +# Repository structure + +A monorepo. Three top-level directories hold code; the rest is repository-wide. What the move still owes is at the bottom of this page. + +``` +. +├── hyperformula/ # ── package: the calculation engine (published) +│ ├── src/ # Source code +│ │ ├── HyperFormula.ts # Main engine class, public API entry point +│ │ ├── parser/ # Formula parsing (Chevrotain parser generator) +│ │ ├── interpreter/ # Formula evaluation +│ │ │ ├── plugin/ # Built-in spreadsheet function plugins +│ │ │ └── functionMetadata/ # Human-readable metadata for every built-in +│ │ ├── DependencyGraph/ # Cell dependency tracking and recalculation order +│ │ ├── dependencyTransformers/ # AST rewrites when rows/columns/sheets move +│ │ ├── i18n/languages/ # Function-name translations, one file per language +│ │ ├── format/ helpers/ Lookup/ statistics/ +│ ├── test/ # Smoke tests; the private suite mounts here +│ │ ├── smoke.spec.ts # Public smoke tests +│ │ ├── fetch-tests.sh # Clones/updates the private test repository +│ │ └── hyperformula-tests/ # Private suite (git-ignored, branch-matched) +│ ├── dev-docs/ # the engine's own internals reference +│ ├── script/ # its build checks: check-file, check-publish-package, if-ne-env +│ ├── .config/ # webpack, karma, and babel config factories +│ ├── tsconfig.json jest.config.js karma.conf.js webpack.config.js +│ ├── babel.config.js ht.config.js jasmine.json .npmignore +│ ├── .typedoc.ts .typedoc.md.ts # API reference generation, output into docs/api +│ ├── package.json .nvmrc README.md LICENSE.txt +│ └── AGENTS.md CLAUDE.md +│ +├── hyperformula-ui/ # ── package: UI components (not imported yet) +│ +├── docs/ # ── the documentation portal (NOT a workspace member) +│ ├── guide/ # Markdown guides +│ ├── api/ # API reference (generated; git-ignored) +│ ├── examples/ # Code examples embedded in guides +│ ├── .vuepress/ # VuePress configuration, theme, components, plugins +│ ├── script/ # Generates guide/built-in-functions.md; composes the Worker assets +│ ├── worker/index.js # Cloudflare Worker serving the built portal +│ ├── wrangler.jsonc # Its deploy configuration +│ ├── package.json .nvmrc +│ └── AGENTS.md CLAUDE.md README.md +│ +├── script/ # Repository-wide only: the release procedure and the licence gate +├── dev-docs/ # Repository-wide reference (this directory; start at README.md) +├── .ai/ # One sentence pointing at dev-docs/, for agents that look here +├── .claude/ # Claude Code settings and skills +├── .github/ # CI workflows, issue and PR templates +├── .eslintrc.js .eslintignore # Linting, run once from the root over everything +├── package.json # Private workspace root: fan-out scripts only +├── package-lock.json .nvmrc .worktreeinclude +├── AGENTS.md CLAUDE.md README.md CONTRIBUTING.md LICENSE.txt +├── CHANGELOG.md # one history for every package +└── CODE_OF_CONDUCT.md +``` + +## Workspaces + +`workspaces` in the root `package.json` lists `hyperformula`. `npm ci` at the root installs it into a shared `node_modules`. `hyperformula-ui/` is a placeholder and is deliberately not listed yet — see [What the move still owes](#what-the-move-still-owes). + +**`docs/` is deliberately outside the workspace.** The portal drags in a large, old dependency tree (VuePress 1.x, `--openssl-legacy-provider`) that must not reach an engine install. It has its own `package.json` and installs separately with `npm run docs:install`. + +## Where a command runs + +| Command | Runs in | +|---|---| +| `npm run lint` | The root, over the whole repository | +| `npm run test:jest`, `bundle-all`, `compile` | Fanned out to `hyperformula` | +| `npm run docs:*` | Orchestrated from the root across both `hyperformula` and `docs` | + +Run a package's own scripts from its directory, or with `--workspace=hyperformula`. See [`BUILD.md`](BUILD.md). + +## Build outputs + +All git-ignored, all under `hyperformula/`: `lib/` (`tsc` output, the input to every bundle), `es/`, `commonjs/`, `dist/`, `languages/`, `typings/`. The portal's output is `docs/.vuepress/dist/`, and `docs/api/` plus `docs/guide/built-in-functions.md` are generated. + +Never edit any of them. Never read the **build artifacts** — `lib/`, `es/`, `commonjs/`, `dist/`, `languages/`, `typings/`, `docs/.vuepress/dist/` — to answer a question about behaviour; read `hyperformula/src/` instead, and the agent deny list in `.claude/settings.json` enforces that. + +`docs/api/` and `docs/guide/built-in-functions.md` are the exception. They are generated, so editing them is pointless, but they *are* the API reference and the function reference and reading them is often exactly right. + +## `dev-docs/` at two levels + +This directory holds what applies to the whole repository: the definition of done, code style, testing standards, documentation rules, the build and release process, pull requests, worktrees, and the agent setup. + +[`hyperformula/dev-docs/`](../hyperformula/dev-docs/README.md) holds the engine's internals: architecture, the parser, the interpreter, the dependency graph, the function catalogue, translations, performance, its test suites, and its own build steps. + +The split is by ownership. A fact lives in exactly one of them, and `hyperformula-ui` gets its own when it lands. + +## Scripts at three levels + +Each script lives with whatever invokes it, and every one has exactly one caller: + +| Directory | Holds | +|---|---| +| `script/` | `release/` and `check-licenses.mjs` — both span the whole repository | +| `hyperformula/script/` | `check-file.js`, `check-publish-package.js`, `if-ne-env.js` — called by the engine's build | +| `docs/script/` | the built-in-functions generator and `prepare-cf-assets.js` — called by the portal | + +## Directories with their own `AGENTS.md` + +`hyperformula/`, `hyperformula/src/`, `hyperformula/src/parser/`, `hyperformula/src/interpreter/`, `hyperformula/src/interpreter/plugin/`, `hyperformula/src/interpreter/functionMetadata/`, `hyperformula/src/DependencyGraph/`, `hyperformula/src/i18n/`, `hyperformula/test/`, `docs/`, and `script/`. + +Each is a pointer of a few lines — what the directory is, and which `dev-docs/` page or local `README.md` holds the detail. They load automatically when an agent reads a file in that subtree, so they stay small on purpose. + +## What the move still owes + +1. **Import `hyperformula-ui`.** The directory is a placeholder; the package is imported from the formula-builder repository in a separate change, preserving its history, and it keeps the scope it publishes under today. Add `hyperformula-ui` to the root `workspaces` array in that same change, not before: npm silently ignores an entry with no `package.json`, so listing it early buys nothing and the lockfile has to be regenerated when the package lands either way. When it arrives it also needs an `.nvmrc` saying `22` and an `AGENTS.md` with a `CLAUDE.md` symlink — but no changelog of its own, and its version moves in step with the engine's. The release script bumps one manifest today; give it the second one in the same change. +2. **Path-filter CI.** Each package's jobs should run only when its own paths change, with full runs on `develop`, `master`, and release branches. Not done here on purpose: a naive `paths:` filter on a workflow that branch protection lists as a required check leaves the check permanently pending, and pull requests become unmergeable. Doing it safely needs the required-checks list, which lives in repository settings rather than in the tree, and the `dorny/paths-filter`-plus-single-gate shape that the Handsontable monorepo uses. + +## What the move decided + +- **npm workspaces, not pnpm.** A package-manager migration is a risk the move did not need to carry at the same time. +- **`docs/` is not a workspace member.** VuePress 1.x and its `--openssl-legacy-provider` dependency tree must never reach an engine install. It installs on its own with `npm run docs:install`, and CI installs it before building the portal. +- **`dev-docs/` at two levels, not one at the root.** One directory at the root was the original plan; it was dropped in favour of splitting by ownership, so that each package's internals travel with the package and `hyperformula-ui` gets its own when it lands. Repository-wide standards stay here, and a fact lives in exactly one level — see [`dev-docs/` at two levels](#dev-docs-at-two-levels). +- **The packages release together, on one version, from one changelog.** A release cuts every published package at the same version, whether or not each one changed, and `CHANGELOG.md` at the repository root is the single history for all of them. That keeps one number to reason about — the version a user reports a bug against identifies the state of the whole repository — at the cost of publishing a package whose code did not move. Entries name the package they concern where it is not obvious. +- **The published tarball still carries a changelog.** `hyperformula`'s `prepack` copies the root `CHANGELOG.md` into the package and `postpack` removes it again, so there is one file under version control and npm consumers still get one. +- **Every `.nvmrc` says `22`.** +- **Linting stays at the root**, run once over the whole repository, so nothing between packages falls through the gap. +- **The private test suite stays branch-matched.** Only its checkout path moved, to `hyperformula/test/hyperformula-tests/`. Its specs needed no change: they import the engine relatively, and the depth from a spec to the package root is unchanged. + +## Two things the move uncovered + +Both were pre-existing, and both are recorded here because the next person will otherwise rediscover them the hard way. + +- **The source language packs were never linted.** The old ignore list carried a bare `languages` entry meant for the build output. An unanchored pattern matches a directory of that name at any depth, so it also excluded `src/i18n/languages/`, and the `sort-keys` override targeting those files never ran. Anchoring the build-output entry exposed 881 violations. They are excluded again, deliberately and with a comment, in [`.eslintignore`](../.eslintignore); sorting 19 translation files is a change of its own. +- **`@vuepress/shared-utils` only works inside a full VuePress dependency tree.** It requires `markdown-it-emoji` and a `lru-cache` major it does not declare, and relied on `vuepress` hoisting them. That is why the built-in-functions generator moved into `docs/script/`, where that tree exists, rather than staying beside the engine build scripts. diff --git a/dev-docs/TESTING.md b/dev-docs/TESTING.md new file mode 100644 index 0000000000..1e2004cc19 --- /dev/null +++ b/dev-docs/TESTING.md @@ -0,0 +1,65 @@ +# Testing standards + +What a change must prove, and how a test case is written. These apply to every package in the repository. + +Each package documents its own suites and commands: the engine's are in [`hyperformula/dev-docs/TESTING.md`](../hyperformula/dev-docs/TESTING.md). + +## What a change must cover + +- Every change to `hyperformula/src/` needs tests in `hyperformula/test/`. This is part of the [definition of done](DEFINITION-OF-DONE.md), not a suggestion. +- **Bug fix**: at least one test that reproduces the bug — it must fail against the unfixed code. Write it first and watch it fail. +- **New feature**: a set of tests that describe the feature precisely enough to serve as its specification. +- Cover more than the happy path: boundary values, empty and invalid input, error results, and interaction with related features. +- `docs/` and the `script/` directories are not tested. + +## How to write a test case + +```ts +it('returns the divisor sign for arguments with opposite signs', () => { + const engine = HyperFormula.buildFromArray([['=MOD(-3, 12)']]) + + expect(engine.getCellValue(adr('A1'))).toBe(9) +}) +``` + +- **One assertion per test case.** Each case is very simple and focused. Split rather than adding a second `expect`. +- **No control flow in a test case.** No loops, no conditionals. A parameterised loop hides which input failed; write the cases out. +- Name the case after the behaviour it pins, not after the function under test: "returns `#VALUE!` when the range is empty", not "test SUMIFS". +- Build the smallest engine that exhibits the behaviour. A two-cell array beats a realistic sheet. +- Assert through the public API — `getCellValue`, `getCellFormula`, `getSheetValues` — not through internals. +- A test must prove intended behaviour. Never relax an assertion, widen a matcher, or skip a case to turn a run green — if a test is red, the default assumption is that the **code** is wrong. + +Before requesting a review, ask which further tests would be valuable and add the ones that protect against realistic regressions. + +## A test must prove behaviour + +A test that passes without proving anything is worse than no test: it occupies the space where the real test would have gone, and it makes the next reader believe the behaviour is covered. + +Write the case from the requirement, not from the implementation. Reading the implementation first and then writing a test that mirrors it produces a test that passes for any implementation, including the wrong one. + +**When a test is red, the default assumption is that the code is wrong.** Change the test only when you can state, in one sentence, why its expectation was wrong — and that sentence must be about the specification, not about the effort of fixing the code. + +### Banned ways of going green + +- Relaxing an assertion: an exact value to `toBeCloseTo`, a specific error to "some error", `toEqual` to `toContain`. +- Deleting the assertion that fails and keeping the ones that pass. +- Adding `.skip` or `.todo`, or commenting out a case that used to run. +- Widening a matcher until every implementation passes. +- Catching the error the code should not be throwing, and asserting that it was caught. +- Mocking the unit under test, or mocking so deeply that only the mock is exercised. +- Asserting that a call "does not throw" when the requirement is a specific returned value. +- Changing the input until the current implementation happens to be right. + +"This test fails and I do not yet know why" is a useful report. A green run that hides it is not. + +### Hollow assertions + +These execute code and prove nothing. Assert the value the specification names. + +```ts +expect(engine.getCellValue(adr('A1'))).toBeDefined() // any value passes +expect(() => engine.setCellContents(...)).not.toThrow() // any non-throwing bug passes +expect(result).toBeTruthy() // 1, 'x', and [] all pass +``` + +Never claim a test passes without having run it, and never claim a fix works because the reasoning is sound. diff --git a/dev-docs/WORKTREES.md b/dev-docs/WORKTREES.md new file mode 100644 index 0000000000..ff5e8b9c05 --- /dev/null +++ b/dev-docs/WORKTREES.md @@ -0,0 +1,32 @@ +# Working in a linked git worktree + +Claude Code can run a session — or an isolated subagent — in a `git worktree` so its changes stay off your main checkout. `git worktree` materialises **tracked files only**, so a fresh worktree is not a working checkout until you bootstrap it. + +## What is missing, and what to do about it + +| Missing | Why it matters | Fix | +|---|---|---| +| `node_modules/` | Nothing runs. | `npm ci` in the worktree | +| `hyperformula/test/hyperformula-tests/` | The private suite is git-ignored, so every `npm run test:jest` run covers only the smoke tests | `npm run test:setup-private` | +| `hyperformula/{lib,dist,es,commonjs,typings,languages}/` | `npm run test:browser` and the bundle checks have nothing to run against | `npm run bundle-all` | +| `docs/api/`, `docs/guide/built-in-functions.md` | The docs build fails, or serves nothing | `npm run docs:build` | +| `.dev.vars*` | `wrangler` deploy and preview fail | Copy it yourself if you need it. Deploy credentials are deliberately **not** copied into worktrees — see [`.worktreeinclude`](../.worktreeinclude) | + +## The branch-matched test suite is the trap + +`hyperformula/test/fetch-tests.sh` checks out the branch of the **same name** in the private test repository. Two consequences in a worktree: + +1. Copying `hyperformula/test/hyperformula-tests/` from the main checkout brings the *other* branch's tests. They will run, and they will report results that have nothing to do with the code in front of you. `.worktreeinclude` copies nothing at all, for this reason among others. +2. Run `npm run test:setup-private` once per worktree, and again after any branch switch inside it. + +## Do not symlink `node_modules` + +`worktree.symlinkDirectories` used to point each worktree's `node_modules/` at the main checkout's copy. It was removed when the repository became a workspace, and should not come back. + +A root-level symlink was safe while there was exactly one `node_modules/.bin`. In a workspace it is not: npm may place a package-local `node_modules/.bin` under a package, which the symlink does not cover, and scripts then die mid-build with a bare `command not found`. Worse, `npm ci` inside a worktree whose `node_modules` is a symlink installs *through* it, so the main checkout and every other worktree silently get that branch's dependency tree. + +Run `npm ci` in the worktree. The portal is a separate install either way — `npm run docs:install`. + +## Sparse checkouts + +`worktree.sparsePaths` limits what git writes to disk. Worth setting once a task can be scoped to one package — `[".claude", "hyperformula"]` for engine work. It is not set by default, because a task that turns out to span packages then fails in a confusing way. diff --git a/docs/.nvmrc b/docs/.nvmrc new file mode 100644 index 0000000000..2bd5a0a98a --- /dev/null +++ b/docs/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/docs/.vuepress/build.config.js b/docs/.vuepress/build.config.js index 633ea41f32..85a777e5d8 100644 --- a/docs/.vuepress/build.config.js +++ b/docs/.vuepress/build.config.js @@ -2,11 +2,11 @@ * Docs build configuration. * Override any of these via environment variables: * DOCS_BASE — public base path (must start and end with `/`) - * DOCS_DEST — output directory (relative to repo root) + * DOCS_DEST — output directory (relative to the docs/ package root) * DOCS_HOSTNAME — absolute origin used for the sitemap */ module.exports = { base: '/docs/', - dest: 'docs/.vuepress/dist/docs', + dest: '.vuepress/dist/docs', hostname: 'https://hyperformula.handsontable.com', }; diff --git a/docs/.vuepress/components/graph.vue b/docs/.vuepress/components/graph.vue index 9c518f1968..edfccddffd 100644 --- a/docs/.vuepress/components/graph.vue +++ b/docs/.vuepress/components/graph.vue @@ -5,7 +5,7 @@