# Changelog

All notable changes to `@yaoc1996/js-tools` are documented here. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — any change in the **Breaking changes** section requires a major version bump.

Downstream agents: see [`UPGRADING.md`](./UPGRADING.md) for the upgrade procedure. The decision between Workflow A (guided) and Workflow B (audit) depends on whether this file has entries covering the version range being crossed.

## [Unreleased]

### Breaking changes
- _none yet_

### Added

### Changed

### Deprecated

### Removed

### Fixed

---

## [2.4.0] — 2026-05-29

### Fixed

- `@yaoc1996/js-tools/las` — `LasPointCloud.parse` and `LasPointCloud.write` no longer require serving the `laz-perf` WASM yourself. The worker's `locateFile` always rewrote the path as `${wasmPath}/laz-perf.wasm`, so the default empty `wasmPath` produced the origin-absolute `/laz-perf.wasm`, which 404'd. The `laz-perf` WASM is now bundled alongside the worker (mirroring the IFC worker) and resolved through the worker's own URL; `wasmPath` remains an optional override for self-hosting. Removed the now-dead `build:wasm` copy step.

---

## [2.3.2] — 2026-05-26

### Fixed

- `@yaoc1996/js-tools/ifc` — fixed a regression in 2.3.0 where `IfcModel.load` always threw `Cannot read properties of undefined (reading 'SetLogLevel')`. The worker called `ifcApi.SetLogLevel(...)` before `ifcApi.Init()`; that delegates to `this.wasmModule.SetLogLevel(level)`, but `wasmModule` is `undefined` until `Init()` populates it. Removed the redundant call — `Init()` already sets the log level to `LOG_LEVEL_ERROR` internally.

---

## [2.3.1] — 2026-05-23

### Fixed

- `@yaoc1996/js-tools/mapbox` — fixed a regression in 2.3.0 where `projectGeometryToMapboxCoords` and `projectGeometriesToMapboxCoords` would crash on every `useWorker: true` call. The worker called `worldAnchor.meterInMercatorCoordinateUnits()`, but Comlink uses structured-clone semantics across the worker boundary and strips `MercatorCoordinate`'s prototype methods. The main thread now precomputes the meter scale and ships it as a plain number; the worker reconstructs an anchor shim before calling the helper. Added a regression test.

---

## [2.3.0] — 2026-05-23

Comlink migration + IFC pipeline overhaul. The internal worker plumbing was rewritten on top of [comlink](https://github.com/GoogleChromeLabs/comlink); the removed `worker-utils` module, `WebWorker`/`MapboxWorker`/`LasWorker`/`IfcWorker` classes, and `IfcJobMap` type had no external consumers (verified — Tier-3 deprecated since 2.1.0), so this ships as a minor rather than a major.

### Added

### Changed

- `@yaoc1996/js-tools` — internal worker plumbing migrated to [`comlink`](https://github.com/GoogleChromeLabs/comlink). The previous `worker-utils` module (`WebWorker`, `WebWorkerPool`, `WorkerMain`, `sendProgress`, `sendResults`, `OnProgress`, `WorkerJob`, `JobDefinitionMap`) is gone — these were Tier-3 deprecated since 2.1.0 with no known external consumers. Use `comlink` directly: `wrap()` / `expose()` replace `WebWorker` / `WorkerMain`; `Comlink.proxy(callback)` replaces the `onProgress` option; `Comlink.transfer(value, [buffers])` replaces the `transfer` option.
- `@yaoc1996/js-tools/mapbox` — replaced the `MapboxWorker` class with `createMapboxWorker()` returning `{ api, terminate }` where `api` is a Comlink-wrapped `MapboxWorkerApi`. The worker-side message type `project_geometry` is replaced by the method name `projectGeometries`, which now accepts an array of position buffers in a single call (`positions: ArrayBufferLike[]`) and returns `{ positions: ArrayBuffer[] }`. The `MapboxJobMap` type is replaced by `MapboxWorkerApi`.
- `@yaoc1996/js-tools/las` — replaced the `LasWorker` class with `createLasWorker()` returning `{ api, terminate }`. Worker methods are `parsePoints` and `writePoints` (formerly `parse_points` / `write_points`). The `LasJobMap` type is replaced by `LasWorkerApi`.
- `@yaoc1996/js-tools/ifc` — replaced the `IfcWorker` class with `createIfcWorker()` returning `{ api, terminate }`. Worker method is `loadModelData` (formerly `load_model_data`). `IfcModel.load(params, onProgress?)` keeps the same external signature but now takes `LoadModelDataParams` instead of `IfcJobMap["load_model_data"]["params"]`. The `IfcJobMap` type is replaced by `IfcWorkerApi` plus `LoadModelDataParams` / `LoadModelDataResult`.
- New runtime dependency: `comlink` (~1.5 KB).
- `@yaoc1996/js-tools/mapbox` — added `projectGeometriesToMapboxCoords(geometries, anchor, options?)` for batching multiple geometry projections through a single worker round-trip. Accepts both a single `BufferGeometry` and `BufferGeometry[]`. `projectGeometryToMapboxCoords` remains a single-geometry convenience wrapper.
- `@yaoc1996/js-tools/mapbox` — `projectPositionsToMapboxMercators` rewritten for performance: Web Mercator math is inlined (no per-vertex `MercatorCoordinate.fromLngLat()` object allocation), the function now runs in a single pass with no intermediate `number[]` buffer, and a new optional fifth parameter `out?: Float32Array` lets callers write in place (the worker now overwrites transferred input buffers — zero allocations on the worker side). Latitudes outside ±85.0511° are clamped to mercator's valid range (previously produced `±Infinity`).
- `@yaoc1996/js-tools/mapbox` — **behavior change**: `projectPositionsToMapboxMercators` now uses `worldAnchor.meterInMercatorCoordinateUnits()` (hoisted out of the per-vertex loop) instead of recomputing the meter scale at each vertex's latitude. For typical city/site-scale geometries (sub-100 km) this is numerically indistinguishable; for continent-scale geometries z-heights may drift by a few percent at the extremes. If you need the previous per-vertex behavior, project on the main thread with your own loop.
- `@yaoc1996/js-tools/ifc` — IFC loader is now schema-agnostic. `IfcModel.load` works on IFC2X3, IFC4, and IFC4X3 files (previously threw on anything but IFC2X3). Spatial structure and property extraction now use `web-ifc`'s built-in `properties.*` helpers; the hand-rolled IFC2X3-only walkers are removed.
- `@yaoc1996/js-tools/ifc` — `IfcSpatialNode` gains a `globalId` (IFC GUID) field. The legacy `elements` field is undefined for IFC4 / IFC4X3 — `IfcRelAggregates` and `IfcRelContainedInSpatialStructure` are flattened into a single `children` tree by the cross-schema walker. IFC2X3 files retain populated `elements` for backwards compatibility.
- `@yaoc1996/js-tools/ifc` — property normalization now covers `IfcPropertyEnumeratedValue`, `IfcPropertyListValue`, `IfcPropertyTableValue`, `IfcComplexProperty`, and `IfcElementQuantity` variants in addition to `IfcPropertySingleValue`. Values are unwrapped from IFC scalar wrappers (`IfcLabel` / `IfcText` / `IfcReal` / etc.) and returned as plain JS primitives, arrays, or nested objects.
- `@yaoc1996/js-tools/ifc` — IFC worker bundles `web-ifc.wasm` as a sibling asset; consumers no longer need to copy the wasm into their public directory or pass `wasmPath`. `LoadModelDataParams.wasmPath` is now optional and acts as an override.
- `@yaoc1996/js-tools/ifc` — worker uses `StreamAllMeshes` instead of `LoadAllGeometry`, eliminating the in-memory `Vector<FlatMesh>` for large models. `OpenModel`/`CloseModel` are now wrapped in `try/finally` so malformed files no longer leak the model handle. The Float64→Float32 centroid centering is now a single pass instead of two.
- `@yaoc1996/js-tools/ifc` — `computeEdges` rewritten to avoid per-triangle `Vector3` allocations and to use integer-quantized vertex hashing instead of `toFixed()` string concatenation.
- `@yaoc1996/js-tools/three-utils` — `SubsetFeatureInfo.indices` widened from `number[]` to `ArrayLike<number> & Iterable<number>`. Allows passing `Uint32Array` directly (which is what the IFC pipeline emits) and removes the `Array.from(indices)` copies. Existing `number[]` callers continue to work.

### Deprecated

### Removed

- `package.json` `build:wasm` no longer copies `web-ifc.wasm` to `dist/` — esbuild's file loader emits it as a sibling of the worker bundle (`dist/workers/web-ifc-[hash].wasm`).
- `@yaoc1996/js-tools/ifc` — dropped the explicit `OpenModel(..., { OPTIMIZE_PROFILES: false })` setting. The default is restored.

### Fixed

- `@yaoc1996/js-tools/ifc` — `IfcModel.load` no longer disposes the transient merged `BufferGeometry` whose attributes are held by reference by the `SubsetGeometry`. Previously the dispose call freed shared WebGLBuffers via the renderer's `WebGLAttributes` WeakMap, causing the next render to re-upload all attributes.
- `@yaoc1996/js-tools/ifc` — Emscripten handles acquired during IFC parsing (`FlatMesh.geometries`, `IfcGeometry`) are now released; the previous worker leaked the outer `Vector<FlatMesh>` from `LoadAllGeometry` and per-mesh handles (now resolved by the switch to `StreamAllMeshes`).
- `@yaoc1996/js-tools/ifc` — IFC4 and IFC4X3 files no longer throw "No spatial structure support for schema …". Cross-schema parsing handles all three actively-used schemas.

---

## [2.2.0] — 2026-05-22

### Added

- `Chart` (`react-utils`) — SVG charting framework with axes, gridlines, tick labels, axis titles, lines, areas, grouped & stacked bars, crosshair, tooltip, vertical annotations, and legend. Snap-on-hover via `useChartSnap`.
- `docs/qa/` — manual QA harness for components; `pnpm qa:dev` runs it.

### Changed

- `Chart.Legend` (`react-utils`) — default anchors changed from `xAnchor=0.7, yAnchor=0.2` to `xAnchor=1, yAnchor=0` so the legend's top-right corner pins to the chart's top-right corner and stays inside the container. (No published consumer — `Chart` first ships in 2.2.0.)

### Fixed

- `Chart` (`react-utils`) — crosshair and tooltip no longer disappear when the cursor moves over an `Area` fill, `Bar`, or other rendered chart layer. Children of `Chart` are now wrapped in a `pointer-events: none` group so the interaction `<rect>` always owns mouse events.
- `Chart.AxisTitle` (`react-utils`) — y-axis title is now rotated -90° (reading bottom-to-top) instead of rendered horizontally, and both axis titles are pushed further from tick labels to prevent overlap.
- `Chart.Bar` (`react-utils`) — hovered/dimmed bar now matches the column under the cursor. Bar slot edges are computed as midpoints between adjacent projected x values (Voronoi regions), aligning the bar's visual extent with `useChartSnap`'s `findNearestIndex` hover region.

---

## [2.1.0] — 2026-05-11

### Added

- `scripts/postinstall.cjs` — one-time install-time hint pointing downstream consumers at `@yaoc1996/js-tools/eslint` so the shipped flat config validates our hooks' `deps` arrays (`exhaustive-deps`, `no-unused-deps`). Idempotent (marker file), skipped in CI, skipped when developing this package itself.

### Changed

- `AGENTS.md` split — orientation/pointer kept in `AGENTS.md`; workflow A (guided) and workflow B (audit) procedures extracted to `UPGRADING.md`. Both files ship in the tarball.
- ESLint configuration is now a single source of truth at `src/js-tools/eslint/index.ts`, shipped as `@yaoc1996/js-tools/eslint`. The repo's own `eslint src` runs against the same flat config consumers extend.
- `tsconfig.json` — `strict: true` enabled. All 31 surfaced type errors fixed without changing public signatures. Downstream consumers compiling our `.d.ts` under stricter settings should see no surface change.

### Fixed

- 16 pre-existing `tsc` errors surfaced during the audit pass; `exhaustive-deps` disables eliminated across `react-utils`, `react-three`, and worker code paths.

### Deprecated

Tier 3 commodity modules — these rebuild well-supported npm packages and add weight to upgrade audits (downstream agents under Workflow B must verify all of them on every version bump, including these). All 60 exported symbols below have `@deprecated` JSDoc with the recommended replacement, but remain fully functional. Removal targeted for a future major release.

| Subpath | Module | Recommended replacement |
|---|---|---|
| `@yaoc1996/js-tools` | `heap.ts` (`Heap`, `HeapItemRef`, `EmptyHeapError`) | `mnemonist/heap` |
| `@yaoc1996/js-tools` | `kdtree.ts` (`KDTree`, `kdPointTree`, `kdSegmentTree`) | `kdbush` or `mnemonist/k-d-tree` |
| `@yaoc1996/js-tools` | `lru.ts` (`LRU`) | `lru-cache` |
| `@yaoc1996/js-tools` | `animation.ts` (`animate`, `transition`, `lazyAnimate`) | `framer-motion` or CSS transitions |
| `@yaoc1996/js-tools` | `cv.ts` (`imageFromDataUrl`, `imageFromCanvas`, `resizeImage`, `cropImage`, …) | inline canvas operations at call site |
| `@yaoc1996/js-tools` | `file-utils.ts` (`downloadFile`, `downloadCSVRows`, `downloadCSVColumns`) | `file-saver` |
| `@yaoc1996/js-tools` | `request.ts` (`request`, `createApiUrl`, `requestGlobalOptions`, `RequestOptions`) | `ky` or native `fetch` |
| `@yaoc1996/js-tools` | `ui-utils.ts` (`observeResize`, `ResizeOptions`) | `ResizeObserver` directly |
| `@yaoc1996/js-tools` | `worker-utils.ts` (`WebWorker`, `WebWorkerPool`, `WorkerMain`, …) | `comlink` |
| `@yaoc1996/js-tools` | `geom.ts` (`centroid`, `dot`, `magnitude`, `normalize`, `pointSegmentDistance`, `projectPointOnSegment`, `Point`, `Segment`) | `gl-matrix` (vec ops) or `@turf/turf` |
| `@yaoc1996/js-tools` | `geom-2d.ts` (`computeConvexHull`, `intersectingPolygons`, `pointInPolygon`, …) | `@turf/turf` or `polygon-clipping` |
| `@yaoc1996/js-tools` | `mbtiles-utils.ts` (`Bounds2D`, `MBTilesManager`, `Tile`, …) | inline tile math at call site |
| `@yaoc1996/js-tools` | `format.ts` (`formatBytes`) | `pretty-bytes` or `Intl.NumberFormat` |
| `@yaoc1996/js-tools` | `functional-tools.ts` (`mapEffect`) | inline or `lodash-es` directly |
| `@yaoc1996/js-tools` | `path-utils.ts` (`join`) | `new URL()` or `path-browserify` |

**Not deprecated** (kept):
- `event.ts` — `EventHandler`, typed event bus with non-trivial generics
- Everything under `react-utils`, `react-three`, `react-mapbox`, `react-google`, `three-utils`, `las`, `ifc`, `mapbox`, `google`, `tailwind`, `eslint` — domain-specific, hard to replace

---

## [2.0.6]

> **Note:** this release ships breaking changes under a patch version. `^2.0.5` consumer ranges will pick it up automatically. Audit before upgrading.

### Breaking changes

- `@yaoc1996/js-tools/react-three` — `useThreeObject`: signature changed and behavior changed.
  - Removed the `Object3D | undefined` overload — the first argument must now be a `CreateObjectEffect<Mode>` function. Pass `() => myObject` instead of `myObject` directly.
  - Added a `deps: DependencyList = []` second argument. The effect now re-runs only when `deps` change (was: re-ran whenever `createObject` identity changed).
  - Removed internal `control.forceUpdate()` calls on add/remove. If you relied on a re-render after object mount/unmount, trigger it yourself.
  - **Migration:** see `docs/migrations/v2.0.5-to-v2.0.6.md` for examples.
- `@yaoc1996/js-tools/react-utils` — `useCallbackOnce`: behavior changed.
  - Added a `deps: DependencyList = []` second argument. The once-gate now resets only when `deps` change (was: reset whenever the `effect` identity changed).
  - With the default `deps=[]`, the wrapper is stable forever and the effect runs at most once across the component lifetime regardless of identity churn. Pass explicit `deps` to opt into the old reset behavior.

### Added

- `CHANGELOG.md`, `AGENTS.md` (later split into `AGENTS.md` + `UPGRADING.md`), `docs/migrations/` scaffolding for downstream upgrade workflows. Future breaking changes will be catalogued here.
- `useThreeObject` and `useCallbackOnce` now accept a `deps: DependencyList` argument.

---

## [2.0.5] — historical baseline

> **Breaking changes for versions ≤ 2.0.5 are not catalogued.** The package was modernized (build system migrated to tsup, dependencies upgraded, public API reshaped) without per-change records. Downstream apps upgrading *from* a version older than 2.0.5 must use **Workflow B (audit upgrade)** in [`UPGRADING.md`](./UPGRADING.md) — there is no migration guide that covers earlier versions.

Starting from the next release, every breaking change will be recorded under `### Breaking changes` with:
- Affected subpath export (e.g. `@yaoc1996/js-tools/react-three`)
- Symbol name
- Before/after signature or behavior
- A one-line migration, or a link to `docs/migrations/v<X>-to-v<Y>.md` for longer guides
