pond-ts 0.54.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,7 +8,8 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
8
8
  under a single `v*` tag, so this file covers them all. Pre-1.0: minor bumps may
9
9
  include new features and type-level changes; patch bumps are strictly additive.
10
10
 
11
- [Unreleased]: https://github.com/pond-ts/pond/compare/v0.54.0...HEAD
11
+ [Unreleased]: https://github.com/pond-ts/pond/compare/v0.55.0...HEAD
12
+ [0.55.0]: https://github.com/pond-ts/pond/compare/v0.54.0...v0.55.0
12
13
  [0.54.0]: https://github.com/pond-ts/pond/compare/v0.53.1...v0.54.0
13
14
  [0.53.1]: https://github.com/pond-ts/pond/compare/v0.53.0...v0.53.1
14
15
  [0.53.0]: https://github.com/pond-ts/pond/compare/v0.52.0...v0.53.0
@@ -55,8 +56,318 @@ include new features and type-level changes; patch bumps are strictly additive.
55
56
 
56
57
  ## [Unreleased]
57
58
 
59
+ ## [0.55.0] — 2026-08-04
60
+
61
+ ### Added
62
+
63
+ - **process:** **`@pond-ts/process` publishes for the first time —
64
+ experimental, pre-1.0.** Computations as data over pond-ts: a processing
65
+ graph authored fluently in application code (or composed as JSON by a saved
66
+ view or a tool-calling model) resolves against a declared op vocabulary and
67
+ runs over a bound `TimeSeries`, with content-addressed caching, provenance,
68
+ and per-node timings on every response. Two entry points: the plan layer at
69
+ `.` and the Node worker pool at `./pool`. The docs section
70
+ ([pond-ts.org/docs/process](https://pond-ts.org/docs/process/)) is listed on
71
+ the site with a TypeDoc API reference; the publication follows the 2026-08
72
+ external audit hardening (all P1 findings fixed and regression-pinned). The
73
+ API is expected to move as friction reports land — pin an exact version.
74
+
75
+ - **charts:** **`<BarChart categories>` now works horizontally** ([PND-HCAT],
76
+ the 2026-08 API review's #3 item) — the funnel / ranking / comparison shape.
77
+ `orientation="horizontal"` puts the categories on the **y** axis as unit
78
+ slots with the value on x, and a `<YAxis>` with no explicit `ticks` **derives
79
+ one label per category by itself**, so the chart no longer needs a
80
+ hand-built `i + 0.5` tick list. It previously threw ("horizontal category
81
+ axes are not yet supported"), which forced consumers to convert their
82
+ categories into ordinal `bins` records _and_ hand-place the labels — the
83
+ workaround the gallery funnel documents.
84
+
85
+ Explicit `<YAxis ticks>` still wins, and vertical categorical charts are
86
+ untouched (categories stay on the container's ordinal x band scale). A new
87
+ internal `RowLayer.binCategories()` channel carries the names to whichever
88
+ axis they land on.
89
+
90
+ - **charts:** **the list family's series door** — `<BarList series={splits}
91
+ label={…}>` / `<BoxList series>` take a `TimeSeries` / `ValueSeries`
92
+ directly (one row per event; exactly-one-of with `rows`), closing the
93
+ "required adapter" gap the 2026-08 API review named: starting from a pond
94
+ series there is no shaping step. The `listRowsFrom*` readers remain for
95
+ record rows. Docs across the charts hub, cheat sheet, and type pages now
96
+ present the series as the whole data contract, with the exported `from*`
97
+ builders re-documented as **interop escape hatches for non-pond data**.
98
+
99
+ - **charts:** **`<BarList>` + `<BoxList>` — standalone ranked row lists** (the
100
+ react-timeseries-charts `HorizontalBarChart` shape, rebuilt as what it
101
+ always was: a table). One DOM row per _entity_ — an interface, a split, a
102
+ symbol — with a label cell (any node, links included), one glyph line per
103
+ configured column on **one shared value scale**, optional data cells
104
+ before/after the glyphs, `sortBy`/`sortDirection` or a full custom
105
+ comparator (missing values sort last either direction), an optional per-row
106
+ expander (`renderExpanded`, keyed on row identity so it survives a re-sort),
107
+ and consumer-owned row selection with an accent edge in the marks register.
108
+ A vertical **baseline rule** at the scale origin anchors the rows to one
109
+ reference (on by default for `<BoxList>`, whose lines float at their lower
110
+ quantile; opt-in for `<BarList>`, whose tracks already show zero), and
111
+ reference **`markers`** (`{ value, label? }`) draw a labelled dotted rule
112
+ through every row in the annotation register — an SLA / capacity line —
113
+ with marker values joining the auto domain fit.
114
+ `<BarList>` draws proportional value bars; its sister `<BoxList>` draws a
115
+ five-number distribution per line — range band, `q1`→`q3` body, median line
116
+ — plus an optional **current-value tick** with a formatted inline label (the
117
+ esnet traffic-by-interface look), using the same quantile vocabulary as the
118
+ canvas `<BoxPlot>` (`lower`/`q1`/`median`/`q3`/`upper`, both-or-neither
119
+ body, quantiles computed upstream — `reduce` facts — never by the chart).
120
+ Styling stays on the one channel: bars resolve `theme.bar[as]`, boxes
121
+ `theme.box[as]`; both built-in themes gain a `box.secondary` role for the
122
+ paired-direction case. Readers `listRowsFromTimeSeries` /
123
+ `listRowsFromValueSeries` build one row per event / axis key. The in-plot
124
+ histogram remains `<BarChart orientation="horizontal">` — the lists are for
125
+ the table-shaped cases it can't be (link labels, cells, expanders, custom
126
+ sort).
127
+
128
+ - **charts:** **`BarStyle.hover` — a distinct hover colour for bars**
129
+ ([#577](https://github.com/pond-ts/pond/issues/577)). A theme may now give
130
+ bars a three-step emphasis — `fill` at rest → `hover` under the pointer →
131
+ `highlight` (plus the outline) when selected. Previously one `highlight`
132
+ served both live states, so hover and select differed only by the presence of
133
+ an outline; `ScatterStyle` has carried distinct rest / selected treatments
134
+ (`outline` vs `selectedOutline`) all along, making bars the less expressive
135
+ layer for the same two-state interaction.
136
+
137
+ **Optional, with a `highlight` fallback**, so no existing theme changes
138
+ meaning or rendering — a theme that wants the distinction adds one colour.
139
+ Selection outranks hover on a bar that is both. Single-series only: a stacked
140
+ or per-bin-coloured bar has no separate highlight colour to replace (it pops
141
+ its _own_ fill, so a red/green volume bar keeps its meaning while live), and
142
+ that convention is unchanged.
143
+
144
+ ### Changed
145
+
146
+ - **charts:** **a bar's capabilities now follow the mark it draws, not the
147
+ prop that fed it** ([PND-BARSEM], the 2026-08 API review's #2 item). A
148
+ **one-column vertical** histogram (`bins` + a single `column`) and a
149
+ one-entry `columns` draw exactly the mark a `series` + `column` chart
150
+ draws, but they used to route through the stacked path purely because of
151
+ which prop supplied them — and so silently lost whole-slot hit-testing
152
+ (#584), the `BarStyle.hover` colour, the cursor readout, stable per-bar
153
+ identity and per-bar decimation. They now take the single-series path, so
154
+ visually identical bars behave identically.
155
+
156
+ **What this changes in practice:** on a one-column histogram, hover and
157
+ click now hit the bar's **whole slot** rather than only the drawn
158
+ rectangle (so the space above a short bar is live, and slots tile the
159
+ axis); the layer gains a cursor readout; and `theme.bar.hover` applies.
160
+ A genuine multi-group stack, `categories`, and horizontal charts are
161
+ unchanged — their segments share a bin's x-range, so only y distinguishes
162
+ them. New reader `barsFromBins` backs the normalized path.
163
+
164
+ **Not a pure widening, in one respect:** dense-bar envelope decimation is
165
+ now live on a one-column histogram (it was single-series-only). It engages
166
+ only once bars fall under ~1px, where it is visually lossless, but at that
167
+ density the per-bar `gap` and highlight give way to envelope rects — pass
168
+ `decimate={false}` to keep every bar drawn. The `colors` map, the
169
+ `theme.bar[<column>]` role and `SelectInfo.label` are all preserved across
170
+ the reroute (each was a silent regression caught in review).
171
+
172
+ This shrinks `BarStyle.hover`'s scope warning from a list of five
173
+ path-accidents to the two real exclusions (a multi-group stack has no
174
+ hover channel on `StackStyle`; `binColors` keeps each bar's own colour by
175
+ design) — which was the acceptance test the task set itself.
176
+
177
+ - **charts:** **column names and source modes are now checked at compile
178
+ time** ([PND-CHARTAPI], the 2026-08 API review's #1 item). Every draw
179
+ layer's column props are derived from the series' schema, so
180
+ `<LineChart series={cpu} column="cpuu" />` — and a numeric prop pointed at
181
+ a string column, where the schema has some other numeric column — fail to
182
+ **compile** instead of throwing at render; the
183
+ same holds for `readout`, the band edges, the box quantiles, and the OHLC
184
+ prices. `<BarChart>`'s props became a **union of its legal source modes**,
185
+ so mixing sources (`series` + `bins`) or column forms (`column` +
186
+ `columns`), or passing `categories` a `column`, are compile errors too.
187
+ `<BarList>` / `<BoxList>` get the same treatment for `rows` XOR `series`,
188
+ which additionally closes the row-type hole #590 documented (annotating a
189
+ callback with a custom row type while passing `series` claimed a shape the
190
+ series door cannot produce).
191
+
192
+ **This narrows what compiles — deliberately.** Code carrying a typo, an
193
+ illegal mode mix, or a lying row annotation stops building; that is the
194
+ point, and each case was already a runtime failure. Two compatibility
195
+ behaviours are preserved on purpose: a **loosely-typed** series
196
+ (`TimeSeries<SeriesSchema>`, e.g. from a helper that doesn't narrow) still
197
+ accepts any column name, because an unparameterized schema leaves the name
198
+ union open and nothing can be checked against it; and `bins` column names
199
+ stay `string`, since they name aggregate fields of a bin record rather than
200
+ schema columns. Note the deliberate distinction: a schema that _does_ name
201
+ its columns but has **no numeric one** rejects every name — there is nothing
202
+ numeric to plot — which is not the same as the loose case.
203
+
204
+ **One new limitation.** Because a layer's props are a union _per series
205
+ kind_, a value typed as _either_ kind (`TimeSeries<A> | ValueSeries<B>` — a
206
+ wrapper that forwards whatever it is given) matches no single member and
207
+ must be narrowed or cast at the boundary. `DurationAxis.stories.tsx` is the
208
+ worked example. The alternative design (one generic over the series type)
209
+ handles that case but changes every props type's public generic parameters;
210
+ the trade is recorded in `spikes/charts-type-seam/REPORT.md`.
211
+
212
+ - **charts:** **a bar's hover / click target is now its whole slot**, not the
213
+ rectangle it draws. A bar _is_ the full width of its interval; the `gap` that
214
+ separates adjacent columns is a display affordance. Hit-testing the drawn
215
+ rect made that affordance interactive — the gap was a dead channel you could
216
+ point at and select nothing, and so was the empty plot space above a short
217
+ bar, even though the x-scrub cursor at that same x reported the bar quite
218
+ happily. `barAt` now tests the bar's full interval width and the full plot
219
+ height, so hover, click and the cursor readout all agree on which bar you are
220
+ on, and slots tile the axis.
221
+
222
+ **Widening, with one exception.** Points that previously selected _nothing_
223
+ now select the bar whose slot they fall in. The exception: a bar whose value
224
+ exceeds an explicit `<YAxis max>` used to draw — and be clickable — above the
225
+ plot top, in the strip a `'top'` axis title reserves; the slot stops at the
226
+ axis domain, so that sliver no longer hits. Everything inside the plot that
227
+ hit before still hits.
228
+
229
+ **It reaches across the full plot height, so it can shadow layers beneath
230
+ it.** The topmost hit wins, so a `<BarChart>` declared _after_ a
231
+ `<ScatterChart>` / `<BoxPlot>` / another `<BarChart>` in the same row now
232
+ claims every hit in its x-range at any y. Declare a bar layer **below** the
233
+ marks that should stay clickable.
234
+
235
+ **Single-series vertical only** — a stacked, `bins`, `categories` or
236
+ horizontal chart still hit-tests the drawn segment, because a stack's
237
+ segments share a bin's x-range and only y tells them apart.
238
+
239
+ Unchanged: a genuine hole between non-contiguous intervals still misses (the
240
+ change makes the drawing gap hittable, it doesn't invent coverage the data
241
+ lacks), a gap (`NaN`) bar owns no slot, and a shared edge goes to the left
242
+ bar — the rule `barIndexAtTime` already documented, so the two now agree by
243
+ construction.
244
+
245
+ ### Fixed
246
+
247
+ - **process:** **audit hardening — five wrong-answer / silent-acceptance paths
248
+ in the plan layer closed** (external Codex audit, 2026-08; all reproduced,
249
+ all regression-pinned). Unit validation of a **picked output** read
250
+ `outputs[0]` instead of the selected output, so a picked `variance` was
251
+ refused where variance was demanded and — worse, silently — accepted where
252
+ price was. `Registry.define()` now rejects duplicate input roles and
253
+ duplicate output ids (both used to _collapse_ at run time rather than fail:
254
+ inputs resolved to the last role, outputs dropped the earlier column),
255
+ invalid param defaults, and `dependsOn` naming unknown params. An op result
256
+ whose length does not match the bound series is refused at the producer —
257
+ it used to ride out unchecked whenever `assemble: false` skipped the only
258
+ length check. The column-selection loop now honours `onError` (an operator
259
+ exception escaped `'collect'`), and a selector naming a nonexistent output
260
+ is a `skipped` entry instead of silently surfacing nothing. Fact provenance
261
+ (`id`, `name`, `op`, `unit`) now wins over a custom fold body's fields, and
262
+ `columnBytes` sums a chunked column's chunks instead of reporting 0 — which
263
+ a byte budget would read as "free".
264
+
265
+ Also: the derived fold slots in both builders now key by **params** —
266
+ `shape({points: 100})` after `shape({points: 20})` silently returned the
267
+ 20-point node — and `shape` itself uses a `ceil` stride, so 200 points
268
+ asked of 399 rows returns ≤200 rather than all 399. The nested JSON Schema
269
+ projection can now express the `PickedOutput` input form, `Host` accepts
270
+ `budgetBytes` (the [PND-PROCCACHE] cap was unreachable from the long-lived
271
+ host shape) and grows `remove(id)`, and CI's package-content check covers
272
+ `@pond-ts/process`. Package remains **unpublished** (`private: true`).
273
+
274
+ A second audit round tightened the same seams. `columnBytes` now counts
275
+ what is actually retained: the **backing buffer's capacity** rather than
276
+ the column's logical length (core documents `_values` as possibly
277
+ oversized, so a one-row column viewing a 1000-slot buffer retains 8,000
278
+ bytes, not 8 — an undercount that defeats the budget), the chunk-offset
279
+ index and the bitmap's real bytes on chunked columns. The
280
+ **request-driven half of a `Host`'s footprint is now boundable**:
281
+ `runAsync` binds a graph per distinct caller-supplied `SourceRef`, so
282
+ `maxSources` caps registry-loaded sources LRU (author-added datasets are
283
+ never evicted), and a `remove()` racing an in-flight load now wins — the
284
+ landing load discards its result instead of resurrecting the dataset.
285
+ And an omitted param now unifies with its explicit default in the fluent
286
+ layer's derived fold slots (`shape()` ≡ `shape({points: 40})`, the same
287
+ rule `specId` applies), while the response labels a computation with the
288
+ **first** slot that named it rather than whichever was declared last.
289
+
290
+ - **charts:** **a hovered or selected single-series bar now pops to full
291
+ opacity** ([#576](https://github.com/pond-ts/pond/issues/576)). `drawBars`
292
+ set `globalAlpha` once to the resting `style.opacity` and never lifted it for
293
+ the highlight **fill** on the single-series path — only for the selected
294
+ bar's outline. The per-bar-`binColors` branch in the same function and
295
+ `drawStacks` both already popped to 1, and `drawBars`' own docstring claimed
296
+ it did too. So on a theme with `opacity < 1` a **hovered** bar (which has no
297
+ outline) barely changed, and a **selected** one read only by its outline.
298
+ All three paths now treat the highlight fill identically.
299
+
300
+ **This changes pixels** on any single-series `<BarChart>` with an alpha'd
301
+ theme: highlighted bars are brighter. A theme that raised its base `opacity`
302
+ to compensate may now want it back down.
303
+
304
+ **It also flattens select against hover**, which is worth knowing before you
305
+ upgrade. The selected bar's outline strokes in `highlight` — previously that
306
+ read as a brighter ring over an alpha'd fill, and it was the main thing
307
+ separating a selected bar from a hovered one. Now the fill underneath is the
308
+ same colour at the same alpha, so only the half of the stroke falling
309
+ outside the rect distinguishes them. Hover is no longer nearly invisible,
310
+ but the two live states are closer together. A theme that needs them clearly
311
+ apart should set the new `BarStyle.hover`.
312
+
58
313
  ## [0.54.0] — 2026-08-02
59
314
 
315
+ ### Fixed
316
+
317
+ - **core:** **`fromArrow` now reads a field's declared Arrow type instead of
318
+ guessing from the runtime shape of `toArray()`** — closing a
319
+ silent-corruption class. The reader worked out what a column held from what
320
+ `toArray()` handed back, which is correct for the types it supports and
321
+ quietly wrong outside them, because Arrow's physical layouts do not all store
322
+ one machine word per logical value. Measured, before the fix: **`Float16`
323
+ ingested `1.5` as `15872`** (its half-float bit pattern — the length matched,
324
+ so nothing caught it), and a **`Decimal128` column with a single null
325
+ ingested `123.45` as `12345`** (the per-element path produced exactly `rows`
326
+ values, so the length check never fired). A dense `Decimal` merely threw the
327
+ wrong error, blaming a length mismatch.
328
+
329
+ The readable set is now an explicit allowlist — `Int` (any width),
330
+ `Float32`/`Float64`, `Date32`/`Date64`, `Time32`/`Time64`, `Timestamp`,
331
+ `Utf8`/`LargeUtf8`/`Utf8View`, `Null` (an all-missing value column), and a
332
+ `Dictionary` of any of those (the encoding is transparent; readability
333
+ follows the value type) — checked per field, on the key and value columns of every
334
+ Arrow door (`TimeSeries.fromArrow`, `ValueSeries.fromArrow`, and the
335
+ flattened key edges). Anything else is refused **by name**, with the cast
336
+ that would fix it: `Decimal` names the float64 precision trade-off, `Float16`
337
+ says to cast, `Bool` names the real reason (the columnar ingest engine
338
+ carries `number` and `string` value columns only). A duck-typed stand-in
339
+ carrying no `typeId` keeps working — the `ArrowTableLike` contract is
340
+ deliberately structural — and gains a width check that catches the Decimal
341
+ shape anyway.
342
+
343
+ Behavioural change worth noting: a `Utf8` **key** now throws on its declared
344
+ type rather than on its shape, so the message names the type and points at
345
+ passing it as a value column instead.
346
+
347
+ ### Changed
348
+
349
+ - **A fold no longer builds a `TimeSeries`** ([PND-PROCTERM]). Every node's
350
+ `compute` widened the source with `appendColumn` for each nested input, so
351
+ an op could call the corpus normally — the studies take
352
+ `(series, { column })`. For a fold that was waste twice over: the column it
353
+ reads is already in its inputs, and it was being packed into a series only
354
+ to be read straight back out.
355
+
356
+ The cost was not incidental. `appendColumn` **boxes a gapped column** on
357
+ the way in, because core's `withColumn` takes values rather than a column —
358
+ 22.4 ms per column at 1M rows. Every rolling study is gapped, so the
359
+ expensive path was the ordinary one.
360
+
361
+ 20 folds × 500k rows, on top of the columnar fold context below:
362
+ **383 → 129 ms** (2.96×), rss 173 → 113 MB. Against the boxed, assembling
363
+ baseline the two changes together are **606 → 129 ms**.
364
+
365
+ A facts-only request now returns no `series` at all, and the upstream
366
+ column still resolves through the node graph rather than the terminal's
367
+ `needed` set — so the failure the plan warned about, a fact silently
368
+ coming back with no value because its column was never selected, cannot
369
+ happen.
370
+
60
371
  ### Added
61
372
 
62
373
  - **`ctx.out` — prepared output buffers for a ranged recompute**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pond-ts",
3
- "version": "0.54.0",
3
+ "version": "0.55.0",
4
4
  "description": "TypeScript-first time series primitives",
5
5
  "license": "MIT",
6
6
  "repository": {