# CHANGELOG.md

All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/).

---

## [1.2.0] - 2025-09-14

### Removed
- `mathjs` dependency. 14MB, 200+ functions. Twelve functions used.

### Added
- Custom math utilities module (`src/math-utils.js`). Addition, subtraction, multiplication, division, a handful of trig functions. `Co-authored-by: chatgpt`

### Changed
- Bundle size reduced by 68%. Build time down from 12s to 4s.

- Module: 47 lines across 1 file. 0 tests. 0 dependencies.

---

## [1.3.0] - 2025-10-03

### Added
- Percentage calculations. The replacement didn't include them.
- Rounding functions (`round`, `ceil`, `floor`). These were being done inline in 14 different places, three different ways.

### Fixed
- Division by zero no longer returns `Infinity`. Accounting flagged this after an invoice rendered a line item total as "$Infinity." (Fixes #127 "Invoice total is $Infinity")
- `round(2.675, 2)` now returns `2.68` instead of `2.67`. Floating point. Added a workaround.
- Rounding workaround broke negative numbers. All credits issued in the last 48 hours were rounded in the customer's favor. Escalation: finance.
- Subtotal calculation was concatenating instead of adding. The pricing form submits values as strings. In JavaScript, `'149.99' + '24.99'` is the string `'149.9924.99'`, not `174.98`. (Fixes #148 "Customer charged $149 million for a $175 order")

- Module: 106 lines across 1 file. 0 tests. 0 dependencies.

---

## [1.4.0] - 2025-10-22

### Added
- Expression parser for user-defined pricing formulas. Sales wanted this for months.

### Security
- **CVE-2025-41547**: Expression parser was using `eval()`. What we were told was a recursive descent parser turned out to be a `Function()` constructor wrapping user input. A customer entered `require('child_process').exec('rm -rf /')` as a pricing formula. WAF caught it. Parser replaced.

- Module: 531 lines across 1 file. 0 tests. 0 dependencies.

---

## [2.0.0] - 2025-11-15

### Changed
- Rewritten. `Co-authored-by: gpt-4o`

### Added
- Matrix operations for reporting pivot tables.
- Statistical functions (`mean`, `median`, `standardDeviation`).
- Arbitrary precision decimals for currency. Previous implementation used IEEE 754 floating point for monetary values.

### Fixed
- Expression parser does not handle operator precedence. `2 + 3 * 4` returns `20`. Enterprise customers have been receiving wrong bulk pricing since October.
- Currency amounts now stored as integers (cents) in the database. Retroactive correction tracked in JIRA-4521 through JIRA-4523.
- Matrix multiplication dimensions were backwards. Dashboard was multiplying a 3x2 by a 2x3 and getting a 2x2 result instead of 3x3.
- Input validation checked `result === NaN` to catch bad calculations. This never catches anything, because `NaN === NaN` is `false` in JavaScript. Invalid calculations were silently passing through to invoices as `NaN`. The PDF renderer prints this as "NaN" in the total field. (Fixes #209 "What does NaN mean and why do I owe it")

- Module: 842 lines across 4 files. 47 tests. 0 dependencies.

---

## [2.0.2-hotfix] - 2025-11-18

### Fixed
- Arbitrary precision decimals don't handle negative numbers. All refunds since November 15 were applied as charges. (Fixes #203 "Refund charged me twice instead of refunding") Escalation: legal. Further details tracked outside version control.

---

## [2.2.0] - 2025-12-17

### Added
- Big number support. JavaScript's `Number.MAX_SAFE_INTEGER` is 9,007,199,254,740,991. Required after a client began invoicing in Indonesian rupiah. Implementation is strings with manual digit-by-digit arithmetic. Addition works. Subtraction works when the result is positive. Multiplication works up to 15 digits.
- Long division. `Co-authored-by: copilot`

### Deprecated
- `divide()`, which silently truncated past 15 digits, renamed to `divideLegacy()`.

### Fixed
- An enterprise client's order for exactly 9,999,999,999,999,999 rupiah was billed as 10,000,000,000,000,000. JavaScript silently rounds integers above `MAX_SAFE_INTEGER` to the nearest representable value. The big number module was supposed to prevent this but was not being called for integer-only amounts. Rounding discrepancy: 1 rupiah.
- Long division infinite loops on repeating decimals. `1 / 3` does not terminate. Added a cap of 1,000 iterations, so `1 / 3` now returns a string with 1,003 characters. (Fixes #256 "Invoice PDF is 47 pages long for a single line item")

- Module: 1,891 lines across 6 files. 74 tests. 0 dependencies.

---

## [3.0.0] - 2026-01-20

### Changed
- Rewritten (third iteration). `Co-authored-by: claude-sonnet`

### Added
- Plugin system for extensibility.

### Removed
- Vendored copy of mathjs that was committed in an earlier session. No commit message references its addition.

- Module: 2,814 lines across 11 files. 340 tests. 0 dependencies.

---

## [3.0.1] - 2026-01-23

### Removed
- Plugin system. Zero adoption. No usage documentation exists.

### Added
- mathjs test suite added to the repository as a reference for expected behavior. Added `test/vendor/` to `.npmignore` so it doesn't ship with the package.

### Security
- Two test files were copy-pasted from a Stack Overflow answer that was itself taken from a GPL-licensed numerical methods textbook. Escalation: legal. Tests rewritten. Assertions are mathematically identical with different variable names.

### Fixed
- `factorial(171)` returns `Infinity`. Technically correct (it exceeds `Number.MAX_VALUE`) but the big number module was supposed to handle this. It doesn't, because `factorial()` calls `multiply()` not `bigMultiply()`. The test suite tests up to `factorial(5)`, which returns 120.
- `min()` with no arguments returns `Infinity`. `max()` with no arguments returns `-Infinity`. This is correct per the JavaScript spec but the pricing engine calls `min()` on an empty array when a product has no discount tiers. (Fixes #271 "Discount: $Infinity") Patched by checking for empty arrays. The check uses `arr.length == false`, which works because `0 == false` is `true` in JavaScript.

- Module: 2,814 lines across 11 files. 340 tests. 4,281 vendored tests. 0 dependencies.

---

## [3.1.0] - 2026-01-29

### Fixed
- Trig functions were treating all inputs as degrees. They should be radians. The original prompt said "make trig functions" and did not specify. (Fixes #285 "Delivery distances are wrong by varying amounts depending on the city") Geospatial calculations incorrect from 2025-10-01 to 2026-01-29.
- A hardcoded `3.14159` instead of `Math.PI` in the billing cycle pro-ration function. Present since 1.2.0. Monthly invoices 0.00005% incorrect for five months.

---

## [3.2.0] - 2026-02-11

### Added
- Logarithms. Finance needs them for compound interest.

### Fixed
- `log(0)` returns `-Infinity`, which is distinct from `Infinity`. Both are valid JavaScript numbers. Both are `typeof 'number'`. Neither is `NaN`. The PDF renderer handles `NaN` (after 2.0.1) but did not anticipate two different infinities. Negative infinity invoices print the minus sign. Positive infinity invoices do not. (Fixes #299 "Invoice shows just a minus sign with no amount")
- `log()` is the natural logarithm. Finance wanted `log10()`. (Fixes #303 "Loan approved at 2.3x the correct interest rate") Disclosure: customer dispute.

- Module: 3,102 lines across 14 files. 340 tests. 4,281 vendored tests. 0 dependencies. 1 README. 1 contributing guide.

---

## [3.3.0] - 2026-02-20

### Security
- Our "zero-dependency" math module imports `Buffer` for the big number implementation. In the browser build this pulls in a 500KB polyfill. Bundle size is now larger than when we had mathjs.

### Changed
- Replaced `Buffer` with `Uint8Array`. Bundle size is down. Performance is down 340%. Big number division takes 8 seconds for anything over 20 digits. Performance regression observed in production.

### Fixed
- Pricing tier validation used chained comparisons: `if (amount > 100 > 50)`. JavaScript evaluates left to right. `amount > 100` returns `true` or `false`, then `true > 50` is `false` and `false > 50` is `false`. Every order was falling into the lowest pricing tier. (Fixes #312 "All customers getting the $5/mo plan")
- `Intl.NumberFormat` grouping separator changed from U+00A0 to U+202F in Chrome 119. Invoice amounts formatted in Chrome no longer pass string equality checks against amounts formatted in Safari or server-side Node 18. (Fixes #315 "Invoice validation fails in Chrome")

---

## [4.0.0-rc.FINAL] - 2026-03-01

### Changed
- Rewritten (spec-driven). Specification: 4,200 words. `Co-authored-by: cursor-agent`

### Fixed
- The spec specified IEEE 754 double-precision floating-point. This is the default JavaScript number type. The generated implementation followed the spec and reintroduced precision issues from versions prior to 2.0.0. Reverted to string-based big numbers from 2.x and integrated them into the 4.x architecture.
- Integration of 2.x big numbers with 4.x created a circular require between `core.js` and `bignum.js`. Fixed by putting everything in one file. `Co-authored-by: devin`

- Module: 4,127 lines across 1 file. 2,100 tests. 4,281 vendored tests. 0 dependencies.

---

## [4.1.0] - 2026-03-18

### Security
- **CVE-2026-10283**: Prototype pollution in the expression parser. `{"__proto__": {"isAdmin": true}}` in the variable context modifies `Object.prototype` for every subsequent request in the process. (Fixes #327 "Expression parser allowed prototype pollution") Disclosure: external. Expression parser rewritten (third iteration).

### Fixed
- `typeof NaN` is `'number'` in JavaScript. The input validation checks `typeof result === 'number'` to confirm calculations succeeded. `NaN` passes this check. (Fixes #331 "Not a Number is a number?") Replaced with `Number.isFinite()`, which rejects `NaN`, `Infinity`, and `-Infinity`.

- Module: 4,612 lines across 1 file. 2,100 tests. 4,281 vendored tests. 0 dependencies.

---

## [4.3.0] - 2026-04-01

### Fixed
- `max(0, -0)` returns `-0`. JavaScript considers `0 === -0` to be true but `Object.is(0, -0)` to be false. (Fixes #342 "How is my balance negative zero dollars") Audit requested written explanation of negative zero.
- Exchange rate conversion for a micro-pricing feature. `parseInt(0.0000001)` returns `1`. JavaScript converts the number to the string `"1e-7"` before parsing, and `parseInt` stops at the first non-numeric character, which is `e`. A per-API-call rate of $0.0000001 was being billed as $1. (Fixes #349 "Am I being charged $1 per API call or $0.0000001")
- Floor price validation used `Number.MIN_VALUE` as a minimum threshold, expecting it to be the smallest number JavaScript can represent. `Number.MIN_VALUE` is `5e-324`. It is positive. It is the smallest *positive* number, not the most negative. The minimum price floor was effectively zero. (Fixes #353 "We are paying customers to use the product")

- Module: 4,891 lines across 1 file. 2,140 tests. 4,281 vendored tests. 0 dependencies.

---

## [5.0.0-beta.FINAL] - 2026-04-05

### Changed
- Split into a monorepo. Seven packages: `@our/math-core`, `@our/math-bignum`, `@our/math-trig`, `@our/math-stats`, `@our/math-matrix`, `@our/math-parse`, `@our/math-utils`. The last one re-exports the other six. `Co-authored-by: windsurf-cascade`

- Module: 6,430 lines across 7 packages. 2,140 tests. 4,281 vendored tests. 6 internal dependencies. 0 external dependencies.

---

## [5.0.0] - 2026-04-08

### Changed
- Collapsed back into a single package. `"workspace:*"` did not resolve when published to npm. Monorepo configuration lacks documentation.

- Module: 6,430 lines across 1 package. 2,140 tests. 4,281 vendored tests. 0 dependencies.

---

## [5.1.0] - 2026-04-15

### Added
- Complex number support. The prompt was "add any missing math functions." `Co-authored-by: gemini-2.5-pro`

### Fixed
- `sqrt(-1)` returns `{re: 0, im: 1}` instead of `NaN`. Several call sites check `isNaN()` to validate input. These now silently accept negative numbers, which propagate as objects through the calculation pipeline until the invoice template renders the total as `[object Object]`.

### Reverted
- Complex numbers.

### Fixed
- Three invoices went out showing "[object Object]" as the amount due. (Fixes #371, #372, #374 "What is [object Object] and why do I owe it")

### Removed
- Two vendored copies of mathjs found in `node_modules/@our/math-utils/node_modules/mathjs` and `node_modules/@our/math-parse/node_modules/mathjs`. Different versions (11.8.0 and 13.2.1). npm did not dedupe them. Neither was declared as a dependency. Origin undetermined.

- Module: 6,430 lines across 1 package. 2,140 tests. 4,281 vendored tests. 0 declared dependencies. Bundle size: 41MB.

---

## [5.3.0] - 2026-05-05

### Fixed
- `mean()` divides by `arguments.length` instead of by the count of values in the array. Called as `mean([1, 2, 3])`, `arguments.length` is 1 because there is one argument: the array. Returns the sum. This has been wrong since 2.0.0. (Fixes #385 "Average order value looks incredible but is it real") The board was pleased with the growth.
- Locale handling. `parseFloat("1.500,00")` returns `1.5` in every locale because `parseFloat` doesn't care about your locale. German and French customers report all amounts over a thousand euros are being billed as single-digit. (Fixes #392 "Billed 1 euro 50 for a 1,500 euro order")
- Sorting. `[1, 5, 11, 2, 23].sort()` returns `[1, 11, 2, 23, 5]`. JavaScript's default sort is lexicographic. (Fixes #398 "Top customers report is alphabetical somehow") Ranking incorrect since launch.

- Module: 6,430 lines across 1 package. 2,140 tests. 4,281 vendored tests. 0 dependencies.

---

## [6.0.0] - 2026-05-15

### Added
- Began reintroducing `mathjs` for "the hard parts." Keeping custom code for "the easy parts."

### Fixed
- Custom `add()` and mathjs `math.add()` return different results for certain floating point inputs. Standardized on mathjs, then custom, then mathjs. Implementation alternates between mathjs and custom.

- Module: 6,430 lines across 1 package. 2,140 tests. 4,281 vendored tests. 1 dependency (14MB).

---

## [6.1.0] - 2026-05-20

### Removed
- Custom implementation.

### Added
- `mathjs`.

- Module: 1 dependency.

---

By Andrew Nesbitt (https://nesbitt.io). Markdown version of https://nesbitt.io/2026/02/16/changelog.html
