@pond-ts/react 0.67.0 → 0.69.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 (5) hide show
  1. package/AGENTS.md +253 -0
  2. package/API.md +88 -83
  3. package/CHANGELOG.md +109 -1
  4. package/README.md +111 -9
  5. package/package.json +23 -5
package/AGENTS.md ADDED
@@ -0,0 +1,253 @@
1
+ # Using pond from a coding agent
2
+
3
+ You are reading this because a project depends on `pond-ts` or one of the
4
+ `@pond-ts/*` packages, or because you are deciding whether it should. This
5
+ file is the shortest route to correct code. It ships inside every pond
6
+ tarball as `AGENTS.md`, next to `API.md` (every public export, one line
7
+ each, with its source file) and `CHANGELOG.md`.
8
+
9
+ Docs: <https://pond-ts.org> · index for agents: <https://pond-ts.org/llms.txt>
10
+ · source: <https://github.com/pond-ts/pond>.
11
+
12
+ ## What pond is, in three lines
13
+
14
+ - A **typed, immutable time series** (`TimeSeries`) whose schema is declared
15
+ once `as const` and narrows every downstream transform — no casts.
16
+ - The **same operator vocabulary on a streaming buffer** (`LiveSeries`):
17
+ push events in, subscribe to incremental `rolling` / `aggregate` views,
18
+ bounded by retention.
19
+ - **Domain packages on top**: React hooks, canvas charts that read a series
20
+ directly, financial studies + trading calendars, fitness analytics, and an
21
+ experimental processing-graph runtime.
22
+
23
+ ## Which package
24
+
25
+ | You need to… | Install | Import from |
26
+ | ----------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------- |
27
+ | Load timestamped rows; bucket, regrid, roll, fill, join, partition them | `pond-ts` | `'pond-ts'` |
28
+ | Ingest a live feed and keep rolling stats over the last N minutes | `pond-ts` | `'pond-ts'` (`LiveSeries`) |
29
+ | Share a series' type across a wire boundary with zero runtime | `pond-ts` | `'pond-ts/types'` |
30
+ | Own / subscribe to a series inside React | `@pond-ts/react` | `'@pond-ts/react'` |
31
+ | Draw it (line, area, band, bar, scatter, box, candlestick, heat map) | `@pond-ts/charts` (+ react, pond-ts) | `'@pond-ts/charts'` |
32
+ | OHLCV bars, SMA/EMA/RSI/MACD/Bollinger/ATR/VWAP…, market-hours calendar | `@pond-ts/financial` | `'@pond-ts/financial'`, `'@pond-ts/financial/fluent'` |
33
+ | GPS / power / heart-rate activity analytics | `@pond-ts/fit` | `'@pond-ts/fit'` |
34
+ | Computations as JSON plans with caching + provenance (experimental) | `@pond-ts/process` | `'@pond-ts/process'` |
35
+
36
+ All six release together under one version and release often. Install with
37
+ `@latest` rather than a version written from memory (a cold-start agent once
38
+ wrote `^0.3.0` and spent turns on a 2024 API); keep their ranges in step — a
39
+ pre-1.0 caret (`^0.67.0`) does **not** span minors.
40
+
41
+ ## The idioms that cover most jobs
42
+
43
+ ### 1. Declare the schema, build the series
44
+
45
+ ```ts
46
+ import { TimeSeries, Sequence } from 'pond-ts';
47
+
48
+ const schema = [
49
+ { name: 'time', kind: 'time' },
50
+ { name: 'host', kind: 'string' },
51
+ { name: 'latencyMs', kind: 'number' },
52
+ ] as const; // ← load-bearing. Without it every column widens to string.
53
+
54
+ const s = TimeSeries.fromJSON({
55
+ name: 'latency',
56
+ schema,
57
+ rows, // positional tuples [time, host, latencyMs] or objects { time, host, latencyMs }
58
+ sort: true, // input not already time-ordered? sort on construction (stable)
59
+ });
60
+ ```
61
+
62
+ Time cells accept ms-since-epoch numbers, `Date`s, or ISO strings **with an
63
+ offset** (`…Z`, `…+01:00`). A wall-clock string with no offset is read as
64
+ **UTC** unless you pass `parse: { timeZone: 'America/New_York' }` — it does
65
+ not throw, it silently shifts by the zone's offset.
66
+
67
+ Other doors: `TimeSeries.fromPoints(points)` for wide `{ ts, a, b }` rows,
68
+ `fromColumns` for struct-of-arrays / `Float64Array`, `fromArrow` for an Arrow
69
+ table, `fromEvents`. `toJSON()` round-trips.
70
+
71
+ ### 2. Downsample, regrid, slide — three different verbs
72
+
73
+ ```ts
74
+ // Fewer rows out than in: one row per bucket.
75
+ const perMin = s.aggregate(Sequence.every('1m'), {
76
+ latencyMs: 'avg', // reducer by column …
77
+ p95: { from: 'latencyMs', using: 'p95' }, // … or a named output; reducers: sum avg min max count first last median stdev pNN
78
+ host: 'last',
79
+ });
80
+
81
+ // Same information, on a regular grid (hold / interpolate). No reduction.
82
+ const gridded = s.align(Sequence.every('10s'), { method: 'hold' });
83
+
84
+ // One output per input event, looking back over a window.
85
+ const rolled = s.rolling('5m', {
86
+ latencyMs: 'avg',
87
+ sd: { from: 'latencyMs', using: 'stdev' },
88
+ });
89
+ ```
90
+
91
+ `Sequence.every()` takes fixed durations only (`'10s'`, `'5m'`, `'1h'`,
92
+ `'1d'`). Months, weeks-in-a-zone, calendar days: `Sequence.calendar('month',
93
+ { timeZone })`. Common shortcuts: `s.baseline('latencyMs', { window: '1h',
94
+ sigma: 2 })` appends avg / sd / upper / lower in one pass;
95
+ `s.outliers(col, { window, sigma })` keeps only the rows outside the band.
96
+
97
+ ### 3. Per-entity, then flatten
98
+
99
+ ```ts
100
+ const perHost = s
101
+ .partitionBy('host') // every stateful operator below runs per host
102
+ .rolling('5m', { latencyMs: 'avg' })
103
+ .collect(); // one flat TimeSeries, `host` carried through (type and runtime)
104
+ // or .toMap() → Map<host, TimeSeries>
105
+ ```
106
+
107
+ `aggregate` and `rolling` under `partitionBy` carry the partition column
108
+ through in both the runtime **and** the static type (since 0.68), so
109
+ `e.get('host')` works on the collected result without naming it. On 0.67 or
110
+ older, name it in the mapping — `{ host: 'first', … }`.
111
+
112
+ ### 4. Clean, fill, join, read out
113
+
114
+ ```ts
115
+ const clean = s.dedupe().fill({ latencyMs: 'hold' }); // also 'linear', 'zero', gap caps
116
+ const joined = a.join(b); // on the time key; see API.md for options
117
+ clean.toPoints(); // [{ ts, host, latencyMs }, …] — chart-library friendly
118
+ clean.toRows(); // positional tuples
119
+ clean.column('latencyMs').mean(); // typed column: min/max/sum/mean/stdev/median/percentile
120
+ clean.column('latencyMs').toFloat64Array(); // zero-copy for canvas / WebGL loops
121
+ ```
122
+
123
+ Everything returns a **new** series. There is no `push` on a `TimeSeries`;
124
+ if you are appending, you want a `LiveSeries`.
125
+
126
+ ### 5. Streaming
127
+
128
+ ```ts
129
+ import { LiveSeries, Sequence } from 'pond-ts';
130
+
131
+ const live = new LiveSeries({
132
+ name: 'latency',
133
+ schema,
134
+ retention: { maxAge: '15m' }, // or { maxEvents: 10_000 }
135
+ ordering: 'reorder', // tolerate late rows …
136
+ graceWindow: '5s', // … up to this late
137
+ });
138
+
139
+ const view = live.partitionBy('host').rolling('5m', { latencyMs: 'avg' });
140
+ const stop = view.on('event', (e) => render(e.get('host'), e.get('latencyMs')));
141
+
142
+ live.push([Date.now(), 'api-1', 42]); // validated against the schema
143
+ live.pushMany(batch);
144
+ const snapshot = live.toTimeSeries(); // immutable batch copy for analytics
145
+ ```
146
+
147
+ `live.aggregate(Sequence.every('1m'), …)` emits `'bucket'` (partial) and
148
+ `'close'` (final) events. Retention bounds memory; `sample({ stride })`
149
+ between `partitionBy` and a long `rolling` bounds it further at firehose
150
+ rates.
151
+
152
+ ### React and charts
153
+
154
+ ```tsx
155
+ import { useLiveSeries } from '@pond-ts/react';
156
+ import {
157
+ ChartContainer,
158
+ ChartRow,
159
+ Layers,
160
+ LineChart,
161
+ YAxis,
162
+ } from '@pond-ts/charts';
163
+
164
+ const [live, snap] = useLiveSeries({
165
+ name: 'latency',
166
+ schema,
167
+ retention: { maxAge: '10m' },
168
+ });
169
+
170
+ <ChartContainer width={800} cursor="crosshair" panZoom>
171
+ <ChartRow height={240}>
172
+ <YAxis id="ms" />
173
+ <Layers>
174
+ {snap && <LineChart series={snap} column="latencyMs" axis="ms" />}
175
+ </Layers>
176
+ </ChartRow>
177
+ </ChartContainer>;
178
+ ```
179
+
180
+ Charts read a pond series directly — do the maths in pond (`rolling`,
181
+ `aggregate`, `align`) and hand the result to a layer. `useLiveSeries`'s snapshot is `null` before the first
182
+ push, hence the guard. `width` is a pixel
183
+ number or `'auto'` (the parent then needs a definite width, or nothing draws).
184
+ Hooks: `useTimeSeries`, `useLiveSeries`, `useSnapshot`, `useLiveQuery`,
185
+ `useDerived`, `useWindow`, `useCurrent`, `useLatest`.
186
+
187
+ ### Financial
188
+
189
+ ```ts
190
+ import '@pond-ts/financial/fluent'; // once, anywhere: adds studies to TimeSeries
191
+ import { TradingCalendar } from '@pond-ts/financial';
192
+
193
+ const studied = bars
194
+ .sma({ period: 20 })
195
+ .rsi({ period: 14 })
196
+ .bollinger({ period: 20 });
197
+ // or, function form: sma(bars, { period: 20 })
198
+ const cal = TradingCalendar.fromRules(
199
+ { timeZone: 'America/New_York', open: '09:30', close: '16:00' },
200
+ { from: '2026-01-05', to: '2026-02-13' },
201
+ );
202
+ ```
203
+
204
+ Studies read `'close'` by default, take **bar-count** periods, append
205
+ columns, preserve row count (warm-up rows are `undefined`). Sixty-plus of
206
+ them; `import { STUDIES } from '@pond-ts/financial/catalog'` lists them at runtime. Session-aligned bars: `ticks.aggregate(cal.barSequence('5m'), {...})`.
207
+
208
+ ## Mistakes agents actually make
209
+
210
+ 1. **Dropping `as const` on the schema.** Everything compiles and every
211
+ column is `string`. If `.get('x')` is not `number | undefined`, this is
212
+ why.
213
+ 2. **`aggregate` when you meant `rolling`, or vice versa.** `aggregate`
214
+ changes the row count (one per bucket); `rolling` keeps it (one per
215
+ event); `align` puts rows on a grid without reducing.
216
+ 3. **`Sequence.every('1M')` for months.** Not fixed-length → use
217
+ `Sequence.calendar('month', { timeZone })`.
218
+ 4. **Wall-clock strings without a zone.** `'2025-01-01T09:00'` is read as
219
+ UTC — no error, every instant shifted by your offset. Add
220
+ `parse: { timeZone }` or use offset strings / ms numbers.
221
+ 5. **Unsorted rows.** The constructor throws and names the row; pass
222
+ `sort: true` rather than sorting by hand.
223
+ 6. **Mutating.** Nothing mutates. Capture the return value.
224
+ 7. **Iterating events in a hot loop for a chart.** Use `column(name)` /
225
+ `toFloat64Array()` or hand the series to `@pond-ts/charts` — do not
226
+ rebuild point arrays per frame.
227
+ 8. **Mismatched package versions.** All `pond-ts` / `@pond-ts/*` at the same
228
+ version, always.
229
+ 9. **Reaching for a chart-library adapter first.** If the project uses React,
230
+ `@pond-ts/charts` consumes the series with no adapter; `toPoints()` is the
231
+ bridge for other libraries.
232
+ 10. **Aggregating in one zone and charting in another.** `Sequence.calendar`
233
+ defaults to **UTC**; a chart's time axis defaults to the **viewer's**
234
+ zone. Pass the same `timeZone` to both — `Sequence.calendar('day', {
235
+ timeZone })` and `<ChartContainer timeZone={timeZone}>` — and a bucket
236
+ edge and the tick that labels it are one instant. A
237
+ `TradingCalendar.fromRules` carries its zone; `calendar={cal}` renders in
238
+ it with no further wiring.
239
+
240
+ ## Where to read next
241
+
242
+ - `API.md` (this folder) — find any export and its source file.
243
+ - <https://pond-ts.org/llms.txt> — every docs page with a one-line
244
+ description; `https://pond-ts.org/llms-<area>.txt` for a single-fetch dump
245
+ of one area (`pond-ts`, `charts`, `financial`, …).
246
+ - <https://pond-ts.org/docs/pond-ts/mental-model> — one picture, and the
247
+ pandas / pondjs translation tables.
248
+ - <https://pond-ts.org/docs/how-to-guides> — end-to-end builds with the
249
+ friction already ironed out (dashboard, messy CSV ingest, histograms,
250
+ large series).
251
+ - Claude Code users: `/plugin marketplace add pond-ts/pond` then
252
+ `/plugin install pond-ts@pond-ts` installs skills for core, charts and
253
+ financial.
package/API.md CHANGED
@@ -40,13 +40,13 @@ next door is the point.
40
40
 
41
41
  ### Series classes & construction
42
42
 
43
- | Export | Purpose | Source |
44
- | ----------------------- | ------------------------------------------------------ | ---------------------------------------------------- |
45
- | `TimeSeries` | Immutable time-indexed collection, columnar storage | `packages/core/src/batch/time-series.ts` |
46
- | `ValueSeries` | Series keyed by a monotonic non-time value axis | `packages/core/src/batch/value-series.ts` |
47
- | `PartitionedTimeSeries` | Scoped view for per-partition stateful transforms | `packages/core/src/batch/partitioned-time-series.ts` |
48
- | `Sequence` | Infinite grid of time buckets (daily, hourly, every N) | `packages/core/src/sequence/sequence.ts` |
49
- | `BoundedSequence` | Finite ordered list of explicit interval buckets | `packages/core/src/sequence/bounded-sequence.ts` |
43
+ | Export | Purpose | Source |
44
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
45
+ | `TimeSeries` | Immutable time-indexed collection, columnar storage | `packages/core/src/batch/time-series.ts` |
46
+ | `ValueSeries` | Series keyed by a monotonic non-time value axis | `packages/core/src/batch/value-series.ts` |
47
+ | `PartitionedTimeSeries` | Scoped view for per-partition stateful transforms; `<S, K, By>` — `By` is the partition column names, carried into `aggregate` / `rolling` result types | `packages/core/src/batch/partitioned-time-series.ts` |
48
+ | `Sequence` | Infinite grid of time buckets: fixed-step (hourly, every N) or calendar (day/week/month/quarter/year in an IANA zone, default UTC) | `packages/core/src/sequence/sequence.ts` |
49
+ | `BoundedSequence` | Finite ordered list of explicit interval buckets | `packages/core/src/sequence/bounded-sequence.ts` |
50
50
 
51
51
  Static constructors on `TimeSeries`: `fromJSON()` (row tuples/objects),
52
52
  `fromColumns()` (struct-of-arrays; `number` + `string` value columns),
@@ -99,13 +99,14 @@ Value-axis wire types
99
99
 
100
100
  ### Temporal keys & events
101
101
 
102
- | Export | Purpose | Source |
103
- | ------------- | --------------------------------------------- | -------------------------------------- |
104
- | `Time` | Point-in-time event key | `packages/core/src/core/time.ts` |
105
- | `TimeRange` | Interval event key (start/end) | `packages/core/src/core/time-range.ts` |
106
- | `Interval` | Labeled time-interval event key | `packages/core/src/core/interval.ts` |
107
- | `Event` | Immutable event: temporal key + typed payload | `packages/core/src/core/event.ts` |
108
- | `toTimeRange` | Coerce temporal values to `TimeRange` | `packages/core/src/core/time-range.ts` |
102
+ | Export | Purpose | Source |
103
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
104
+ | `Time` | Point-in-time event key | `packages/core/src/core/time.ts` |
105
+ | `TimeRange` | Interval event key (start/end) | `packages/core/src/core/time-range.ts` |
106
+ | `Interval` | Labeled time-interval event key | `packages/core/src/core/interval.ts` |
107
+ | `TimeZone` | IANA zone as a calendar: `startOf` / `next` / `parts` / `instant` / `offsetAt` / `abbreviation`; interned, transition-cached; what `Sequence.calendar` buckets with | `packages/core/src/core/time-zone.ts` |
108
+ | `Event` | Immutable event: temporal key + typed payload | `packages/core/src/core/event.ts` |
109
+ | `toTimeRange` | Coerce temporal values to `TimeRange` | `packages/core/src/core/time-range.ts` |
109
110
 
110
111
  ### TimeSeries methods (all in `packages/core/src/batch/time-series.ts`)
111
112
 
@@ -125,10 +126,11 @@ Value-axis wire types
125
126
  `arrayContainsAny()`, `arrayAggregate()`, `arrayExplode()`
126
127
  - **Gap fill / dedupe**: `fill()`, `materialize()`, `dedupe()`
127
128
  - **Aggregate/group**: `aggregate(sequence, spec)`, `reduce()`, `groupBy()`,
128
- `partitionBy()`, `byColumn()` (order-free, by column value),
129
+ `partitionBy()`, `byColumn(col, bins, mapping)` (numeric binning of a column into
130
+ fixed-`width` or explicit-`edges` bins, then reduce per bin — histograms),
129
131
  `rollingByColumn()`, `byValue(axis)` (project onto a `ValueSeries`)
130
132
  - **Windowing/smoothing**: `rolling(window, spec, opts)`, `smooth(column,
131
- method)` (EMA / Butterworth / Savitzky-Golay), `align(method, opts)`
133
+ method, opts)` (`'ema'` / `'movingAverage'` / `'loess'`), `align(method, opts)`
132
134
  - **Differential/statistical**: `diff()`, `rate()`, `pctChange()`,
133
135
  `cumulative()`, `scan()` (custom stateful reducer), `shift()`, `baseline()`
134
136
  (rolling avg/sd/bands), `outliers()` (deviation from baseline)
@@ -157,14 +159,14 @@ Deliberately small — the ordering-based slice of the algebra, no calendar ops
157
159
 
158
160
  ### Key exported types (batch)
159
161
 
160
- | Type group | Names | Source |
161
- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
162
- | Schema contract | `SeriesSchema`, `RowForSchema`, `EventForSchema`, `EventDataForSchema`, `EventKeyForSchema`, `TimeSeriesInput`, `TimeSeriesJsonInput` | `packages/core/src/schema/index.ts` |
163
- | Aggregation specs | `AggregateReducer`, `AggregateMap`, `AggregateOutputMap`, `AggregateSchema`, `BinReducerName`, `BinOutput` | `packages/core/src/schema/index.ts`, `packages/core/src/column.ts` |
164
- | Operation schemas | `RollingSchema`, `RollingAlignment`, `AlignSchema`, `DiffSchema`, `SmoothSchema`, `SmoothMethod`, `FillStrategy`, `FillMapping` | `packages/core/src/schema/index.ts` |
165
- | Column/data kinds | `Column`, `KeyColumn`, `ColumnKind`, `ScalarKind`, `ScalarValue`, `ColumnValue`, `ArrayValue`, `ValidityBitmap` | `packages/core/src/columnar/` |
166
- | JSON wire format | `JsonRowFormat`, `JsonRowForSchema`, `JsonObjectRowForSchema`, `JsonValueForKind`, `JsonTimestampInput`, `JsonTimeRangeInput`, `JsonIntervalInput` | `packages/core/src/schema/index.ts` |
167
- | Temporal utility | `TemporalLike`, `DurationInput`, `CalendarUnit`, `TimeZoneOptions`, `KeyLike`, `BatchSampleStrategy`, `SequenceSample`, `SequenceCoverage` | `packages/core/src/core/`, `packages/core/src/sequence/` |
162
+ | Type group | Names | Source |
163
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
164
+ | Schema contract | `SeriesSchema`, `RowForSchema`, `EventForSchema`, `EventDataForSchema`, `EventKeyForSchema`, `TimeSeriesInput`, `TimeSeriesJsonInput` | `packages/core/src/schema/index.ts` |
165
+ | Aggregation specs | `AggregateReducer`, `AggregateMap`, `AggregateOutputMap`, `AggregateSchema`, `BinReducerName`, `BinOutput` | `packages/core/src/schema/index.ts`, `packages/core/src/column.ts` |
166
+ | Operation schemas | `RollingSchema`, `RollingAlignment`, `AlignSchema`, `DiffSchema`, `SmoothSchema`, `SmoothMethod`, `FillStrategy`, `FillMapping` | `packages/core/src/schema/index.ts` |
167
+ | Column/data kinds | `Column`, `KeyColumn`, `ColumnKind`, `ScalarKind`, `ScalarValue`, `ColumnValue`, `ArrayValue`, `ValidityBitmap` | `packages/core/src/columnar/` |
168
+ | JSON wire format | `JsonRowFormat`, `JsonRowForSchema`, `JsonObjectRowForSchema`, `JsonValueForKind`, `JsonTimestampInput`, `JsonTimeRangeInput`, `JsonIntervalInput` | `packages/core/src/schema/index.ts` |
169
+ | Temporal utility | `TemporalLike`, `DurationInput`, `CalendarUnit`, `TimeZoneOptions`, `Disambiguation`, `ZonedParts`, `ZonedPartsInput`, `StartOfOptions`, `KeyLike`, `BatchSampleStrategy`, `SequenceSample`, `SequenceCoverage` | `packages/core/src/core/`, `packages/core/src/sequence/` |
168
170
 
169
171
  The `pond-ts/types` subpath re-exports the schema-as-contract types with zero
170
172
  runtime (`packages/core/src/schema/public.ts`).
@@ -249,17 +251,17 @@ Types: `UseSnapshotOptions`, `SnapshotSource` (structural — covers
249
251
 
250
252
  ### Components — layout & axes
251
253
 
252
- | Component | Key props | Purpose | Source |
253
- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
254
- | `ChartContainer` | `width`, `range?`, `theme?`, `cursor?`, `panZoom?`, `xScale?`, `bounds?`, `showAxis?`, `calendar?`, `origin?`, `maxBandWidth?`/`bandAlign?`, `onTrackerChanged?`, `onDrawStats?` | Root: shared x-scale, interactions, annotations | `packages/charts/src/ChartContainer.tsx` |
255
- | `ChartRow` | `height`, `cursor?` (deprecated — mount a cursor in the row) | One stacked plot band; owns its y-axes | `packages/charts/src/ChartRow.tsx` |
256
- | `Layers` | children | Mandatory z-stack inside a row (back-to-front) | `packages/charts/src/Layers.tsx` |
257
- | `YAxis` | `id` (req), `side?`, `scale?` (`'linear'` \| `'log'` \| `'symlog'`), `linearWindow?`, `min?`/`max?`, `format?`, `width?`, `hide?` | Y-axis gutter; layers bind via their `axis` prop | `packages/charts/src/YAxis.tsx` |
258
- | `XAxis` | `side?`, `label?`, `format?`, `ticks?`, `transform?`, `dateStyle?` | Placeable x-axis strip; kind inferred from data | `packages/charts/src/XAxis.tsx` |
259
- | `TimeAxis` / `CategoryAxis` | (XAxis props) | Thin `XAxis` presets | `packages/charts/src/TimeAxis.tsx`, `CategoryAxis.tsx` |
260
- | `Canvas` | `width`, `height`, `draw` | Low-level DPR-aware canvas primitive | `packages/charts/src/Canvas.tsx` |
261
- | `Selector` | `enabled?` (default `true`), `selected?` (mark \| set), `hovered?`, `onSelect?`, `onHover?`, `children?` | Wraps its scope; mounting enables click-select and owns the state it drives (RFC A10) | `packages/charts/src/selectors.tsx` |
262
- | `MultiSelector` | `enabled?`, `selected?`, `hovered?`, `sequence?`, `onSelect?`, `onHover?`, `children?` | Sweep-select superset of `Selector`: drag sweeps marks, release reports `(hits, modifiers, spans)` — plural, one per swept layer (RFC A5.2) | `packages/charts/src/selectors.tsx` |
254
+ | Component | Key props | Purpose | Source |
255
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
256
+ | `ChartContainer` | `width`, `range?`, `theme?`, `cursor?`, `panZoom?`, `xScale?`, `bounds?`, `showAxis?`, `calendar?`, `timeZone?`, `origin?`, `maxBandWidth?`/`bandAlign?`, `onTrackerChanged?`, `onDrawStats?` | Root: shared x-scale, interactions, annotations; `timeZone` renders the time axis in an IANA zone (default: viewer-local; a `calendar.timeZone` supplies the default) | `packages/charts/src/ChartContainer.tsx` |
257
+ | `ChartRow` | `height`, `cursor?` (deprecated — mount a cursor in the row) | One stacked plot band; owns its y-axes | `packages/charts/src/ChartRow.tsx` |
258
+ | `Layers` | children | Mandatory z-stack inside a row (back-to-front) | `packages/charts/src/Layers.tsx` |
259
+ | `YAxis` | `id` (req), `side?`, `scale?` (`'linear'` \| `'log'` \| `'symlog'`), `linearWindow?`, `min?`/`max?`, `format?`, `width?`, `hide?` | Y-axis gutter; layers bind via their `axis` prop | `packages/charts/src/YAxis.tsx` |
260
+ | `XAxis` | `side?`, `label?`, `format?`, `ticks?`, `transform?`, `dateStyle?`, `timeZone?` (this strip in another IANA zone) | Placeable x-axis strip; kind inferred from data | `packages/charts/src/XAxis.tsx` |
261
+ | `TimeAxis` / `CategoryAxis` | (XAxis props) | Thin `XAxis` presets | `packages/charts/src/TimeAxis.tsx`, `CategoryAxis.tsx` |
262
+ | `Canvas` | `width`, `height`, `draw` | Low-level DPR-aware canvas primitive | `packages/charts/src/Canvas.tsx` |
263
+ | `Selector` | `enabled?` (default `true`), `selected?` (mark \| set), `hovered?`, `onSelect?`, `onHover?`, `children?` | Wraps its scope; mounting enables click-select and owns the state it drives (RFC A10) | `packages/charts/src/selectors.tsx` |
264
+ | `MultiSelector` | `enabled?`, `selected?`, `hovered?`, `sequence?`, `onSelect?`, `onHover?`, `children?` | Sweep-select superset of `Selector`: drag sweeps marks, release reports `(hits, modifiers, spans)` — plural, one per swept layer (RFC A5.2) | `packages/charts/src/selectors.tsx` |
263
265
 
264
266
  ### Components — draw layers
265
267
 
@@ -385,44 +387,47 @@ Series shapes (same file): `ChartSeries`, `BandSeries`, `BoxSeries`,
385
387
 
386
388
  ### Live values, scales & key types
387
389
 
388
- | Export | Purpose | Source |
389
- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ----------------------------------------- |
390
- | `createLiveValue` / `LiveValue` | Imperative push channel for high-frequency indicator updates (isolated repaint) | `packages/charts/src/indicators.tsx` |
391
- | `scaleTradingTime` / `TradingTimeScale` | Discontinuous time scale collapsing closed-market gaps | `packages/charts/src/tradingTimeScale.ts` |
392
- | `DiscontinuityProvider` | Gap topology consumed by the trading-time scale | `packages/charts/src/tradingTimeScale.ts` |
393
- | `scaleBand` / `ScaleBand` | Ordinal slot scale for the category axis | `packages/charts/src/bandScale.ts` |
394
- | `GapMode` | `'none' \| 'empty' \| 'dashed' \| 'step' \| 'fade'` (Line/Area `gaps` prop) | `packages/charts/src/gaps.ts` |
395
- | `DecimateOption` | `<LineChart decimate>` M4 viewport decimation (`bool \| { threshold }`) | `packages/charts/src/decimate.ts` |
396
- | `CursorMode` | `'none' \| 'line' \| 'point' \| 'inline' \| 'flag' \| 'crosshair' \| 'region'` | `packages/charts/src/context.ts` |
397
- | `TrackerInfo` / `TrackerSample` | Hover readout payload (`onTrackerChanged`) | `packages/charts/src/context.ts` |
398
- | `AnnotationKind` / `CreateSpec` | Annotation identity + draw-gesture payload (`onCreate`) | `packages/charts/src/context.ts` |
399
- | `SelectInfo` | Selection/hover payload (`Selector`/`MultiSelector` `onSelect`/`onHover`) | `packages/charts/src/context.ts` |
400
- | `SelectModifiers` | Keyboard modifiers on a click, 2nd arg to `onSelect` | `packages/charts/src/context.ts` |
401
- | `SelectorProps` | `<Selector>`'s props — `enabled?` / `selected?` / `hovered?` / `onSelect?` / `onHover?` / `children?` | `packages/charts/src/selectors.tsx` |
402
- | `MultiSelectorProps` | `<MultiSelector>`'s props the above plus `sequence?`, with plural callbacks | `packages/charts/src/selectors.tsx` |
403
- | `RangeSpan` | `<RangeCursor onDragRelease>` payload `{ x: [lo, hi], y? }` in axis units | `packages/charts/src/context.ts` |
404
- | `SpanSelection` | Range entry for `selected` one layer's marks over `x`/`y`/`rows` (RFC A5.2) | `packages/charts/src/context.ts` |
405
- | `SelectionEntry` | One `selected` array entry: `SelectInfo \| SpanSelection` | `packages/charts/src/context.ts` |
406
- | `selectionContains` | Is a hit in a mixed selection? The same membership predicate the layers run | `packages/charts/src/span.ts` |
407
- | `sameMark` | Are two hits the same mark? Full identity (`id`, `mark`-or-`key`, `label`) | `packages/charts/src/span.ts` |
408
- | `isSpanSelection` | Entry discriminant narrows a `SelectionEntry` to `SpanSelection` | `packages/charts/src/span.ts` |
409
- | `DrawStatsFrame` / `LayerDrawInfo` | Per-repaint draw-cost + decimation stats (`ChartContainer` `onDrawStats`) | `packages/charts/src/context.ts` |
410
- | `TimeGrain` | Coarse time unit for grain-aware formatting | `packages/charts/src/tickLadder.ts` |
411
- | `SwatchSpec` / `LegendItemInput` | Legend swatch vocabulary + explicit-rows input (`<Legend items>`) | `packages/charts/src/swatch.ts` |
412
- | `useChartLegend` | Headless legend hook: rows (items grouped by chart row) + `hover`/`select` verbs | `packages/charts/src/useChartLegend.ts` |
413
- | `ChartLegend` / `LegendRow` / `LegendItem` | The hook's return shape (`rows` group `items`; items carry `selected`/`hovered`) | `packages/charts/src/useChartLegend.ts` |
414
- | `useChartFrame` | Resolved plot geometry: plot rect, gutters, x scale, a row's y scales, band slot edges | `packages/charts/src/useChartFrame.ts` |
415
- | `ChartFrame` / `ChartFrameRow` | The hook's return shape container x half, plus a row y half that is `null` outside a `<ChartRow>` | `packages/charts/src/useChartFrame.ts` |
416
- | `ChartBands` / `ChartBand` | Ordinal slot geometry on a category axis (`count`/`pitch`/`labels`/`at(i)`); `null` on time/value | `packages/charts/src/useChartFrame.ts` |
417
- | `ChartXScale` | The union the container's shared x scale resolves to (time / linear / trading / band / elapsed) | `packages/charts/src/context.ts` |
418
- | `LegendPlacement` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `packages/charts/src/Legend.tsx` |
419
- | `Curve` | Path interpolation: `'linear' \| 'monotone' \| 'natural' \| 'basis' \| 'step'` | `packages/charts/src/curve.ts` |
420
- | `RadiusEncoding` / `ColorEncoding` | Data-driven scatter size/colour | `packages/charts/src/encoding.ts` |
421
- | `CandleVariant` / `ColorBy` | OHLC mark shape / colouring strategy | `packages/charts/src/ohlc.ts` |
422
- | `AxisFormat` / `CursorFormat` | Tick and cursor-readout formatting (d3 specifier or fn) | `packages/charts/src/format.ts` |
423
- | `AxisTransform` | Monotonic `to`/`from` pair for derived-unit x-axis relabeling | `packages/charts/src/derivedTicks.ts` |
424
- | `AxisMouseEvent` / `AxisMouseHandler` | Axis `onMouseEvent` payload — the mouse event, the axis's `id`, and the value/label under the pointer | `packages/charts/src/axis-events.ts` |
425
- | `Orientation` | Bar growth direction | `packages/charts/src/bars.ts` |
390
+ | Export | Purpose | Source |
391
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- |
392
+ | `createLiveValue` / `LiveValue` | Imperative push channel for high-frequency indicator updates (isolated repaint) | `packages/charts/src/indicators.tsx` |
393
+ | `scaleTradingTime` / `TradingTimeScale` | Discontinuous time scale collapsing closed-market gaps; `scaleTradingTime(provider, { timeZone })` runs its tick ladder and labels in an IANA zone; `.withTimeZone(zone)` / `.timeZone()` re-derive the same mapping in another zone | `packages/charts/src/tradingTimeScale.ts` |
394
+ | `DiscontinuityProvider` | Gap topology consumed by the trading-time scale; optional `withTimeZone(zone)` for providers whose day anchors move with the zone | `packages/charts/src/tradingTimeScale.ts` |
395
+ | `identityProvider` | The gap-free provider a plain continuous time axis runs on; `identityProvider({ timeZone })` puts its day anchors on that zone's midnights | `packages/charts/src/tradingTimeScale.ts` |
396
+ | `TradingCalendarLike` | Structural shape of a trading calendar `ChartContainer calendar` accepts: `discontinuities({ spacing })` + optional `timeZone` (the axis default) | `packages/charts/src/tradingTimeScale.ts` |
397
+ | `ScaleTimeZoneOptions` | `{ timeZone? }` for `scaleTradingTime` / `identityProvider` | `packages/charts/src/tradingTimeScale.ts` |
398
+ | `scaleBand` / `ScaleBand` | Ordinal slot scale for the category axis | `packages/charts/src/bandScale.ts` |
399
+ | `GapMode` | `'none' \| 'empty' \| 'dashed' \| 'step' \| 'fade'` (Line/Area `gaps` prop) | `packages/charts/src/gaps.ts` |
400
+ | `DecimateOption` | `<LineChart decimate>` M4 viewport decimation (`bool \| { threshold }`) | `packages/charts/src/decimate.ts` |
401
+ | `CursorMode` | `'none' \| 'line' \| 'point' \| 'inline' \| 'flag' \| 'crosshair' \| 'region'` | `packages/charts/src/context.ts` |
402
+ | `TrackerInfo` / `TrackerSample` | Hover readout payload (`onTrackerChanged`) | `packages/charts/src/context.ts` |
403
+ | `AnnotationKind` / `CreateSpec` | Annotation identity + draw-gesture payload (`onCreate`) | `packages/charts/src/context.ts` |
404
+ | `SelectInfo` | Selection/hover payload (`Selector`/`MultiSelector` `onSelect`/`onHover`) | `packages/charts/src/context.ts` |
405
+ | `SelectModifiers` | Keyboard modifiers on a click, 2nd arg to `onSelect` | `packages/charts/src/context.ts` |
406
+ | `SelectorProps` | `<Selector>`'s props `enabled?` / `selected?` / `hovered?` / `onSelect?` / `onHover?` / `children?` | `packages/charts/src/selectors.tsx` |
407
+ | `MultiSelectorProps` | `<MultiSelector>`'s props the above plus `sequence?`, with plural callbacks | `packages/charts/src/selectors.tsx` |
408
+ | `RangeSpan` | `<RangeCursor onDragRelease>` payload `{ x: [lo, hi], y? }` in axis units | `packages/charts/src/context.ts` |
409
+ | `SpanSelection` | Range entry for `selected` one layer's marks over `x`/`y`/`rows` (RFC A5.2) | `packages/charts/src/context.ts` |
410
+ | `SelectionEntry` | One `selected` array entry: `SelectInfo \| SpanSelection` | `packages/charts/src/context.ts` |
411
+ | `selectionContains` | Is a hit in a mixed selection? The same membership predicate the layers run | `packages/charts/src/span.ts` |
412
+ | `sameMark` | Are two hits the same mark? Full identity (`id`, `mark`-or-`key`, `label`) | `packages/charts/src/span.ts` |
413
+ | `isSpanSelection` | Entry discriminant narrows a `SelectionEntry` to `SpanSelection` | `packages/charts/src/span.ts` |
414
+ | `DrawStatsFrame` / `LayerDrawInfo` | Per-repaint draw-cost + decimation stats (`ChartContainer` `onDrawStats`) | `packages/charts/src/context.ts` |
415
+ | `TimeGrain` | Coarse time unit for grain-aware formatting | `packages/charts/src/tickLadder.ts` |
416
+ | `SwatchSpec` / `LegendItemInput` | Legend swatch vocabulary + explicit-rows input (`<Legend items>`) | `packages/charts/src/swatch.ts` |
417
+ | `useChartLegend` | Headless legend hook: rows (items grouped by chart row) + `hover`/`select` verbs | `packages/charts/src/useChartLegend.ts` |
418
+ | `ChartLegend` / `LegendRow` / `LegendItem` | The hook's return shape (`rows` group `items`; items carry `selected`/`hovered`) | `packages/charts/src/useChartLegend.ts` |
419
+ | `useChartFrame` | Resolved plot geometry: plot rect, gutters, x scale, a row's y scales, band slot edges | `packages/charts/src/useChartFrame.ts` |
420
+ | `ChartFrame` / `ChartFrameRow` | The hook's return shape container x half, plus a row y half that is `null` outside a `<ChartRow>` | `packages/charts/src/useChartFrame.ts` |
421
+ | `ChartBands` / `ChartBand` | Ordinal slot geometry on a category axis (`count`/`pitch`/`labels`/`at(i)`); `null` on time/value | `packages/charts/src/useChartFrame.ts` |
422
+ | `ChartXScale` | The union the container's shared x scale resolves to (time / linear / trading / band / elapsed) | `packages/charts/src/context.ts` |
423
+ | `LegendPlacement` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `packages/charts/src/Legend.tsx` |
424
+ | `Curve` | Path interpolation: `'linear' \| 'monotone' \| 'natural' \| 'basis' \| 'step'` | `packages/charts/src/curve.ts` |
425
+ | `RadiusEncoding` / `ColorEncoding` | Data-driven scatter size/colour | `packages/charts/src/encoding.ts` |
426
+ | `CandleVariant` / `ColorBy` | OHLC mark shape / colouring strategy | `packages/charts/src/ohlc.ts` |
427
+ | `AxisFormat` / `CursorFormat` | Tick and cursor-readout formatting (d3 specifier or fn) | `packages/charts/src/format.ts` |
428
+ | `AxisTransform` | Monotonic `to`/`from` pair for derived-unit x-axis relabeling | `packages/charts/src/derivedTicks.ts` |
429
+ | `AxisMouseEvent` / `AxisMouseHandler` | Axis `onMouseEvent` payload — the mouse event, the axis's `id`, and the value/label under the pointer | `packages/charts/src/axis-events.ts` |
430
+ | `Orientation` | Bar growth direction | `packages/charts/src/bars.ts` |
426
431
 
427
432
  ---
428
433
 
@@ -617,16 +622,16 @@ erased types. A separate subpath: importing it pulls in every study.
617
622
 
618
623
  ### Trading calendars & sessions
619
624
 
620
- | Export | Purpose | Source |
621
- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
622
- | `TradingCalendar` | Query API: `.sessions()`, `.sessionOn()`, `.isTradingDay()`, `.isOpen()`, `.sessionsInRange()`, `.sessionSequence()`, `.barSequence(period)`, `.tagSessions()`, `.discontinuities()` | `packages/financial/src/calendar/` |
623
- | `generateSessions` | `Session[]` from `SessionRules` over a date range (DST-correct) | `packages/financial/src/calendar/` |
624
- | `normalizeSessions` | Validate + sort an explicit session list | `packages/financial/src/calendar/` |
625
- | `identityDiscontinuity` / `segmentDiscontinuity` / `weekendSkip` | `DiscontinuityProvider`s for the trading-time axis | `packages/financial/src/calendar/` |
626
- | Types | `Session`, `SessionBreak`, `SessionRules`, `DateRange`, `InstantRange`, `TaggedSchema`, `LiveSegment`, `DiscontinuityProvider` | `packages/financial/src/calendar/` |
627
- | `SessionSource` / `SessionAnchorOptions` | The session-anchored studies' input: `TradingCalendar \| Session[]` (`sessions`), or a session-id column name (`session`), plus `stamped` | `packages/financial/src/contract/session-anchor.ts` |
628
- | `PIVOT_METHODS` / `PivotMethod` | The four pivot formula sets (`standard`, `fibonacci`, `woodie`, `camarilla`) | `packages/financial/src/kernels/pivot.ts` |
629
- | `PivotPointsSchema` / `CamarillaPivotPointsSchema` / `PivotPointsResult` | `pivotPoints`' method-dependent appended-schema types (7 columns, or 9 for Camarilla) | `packages/financial/src/studies/pivot-points.ts` |
625
+ | Export | Purpose | Source |
626
+ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
627
+ | `TradingCalendar` | `timeZone` (the exchange zone: `fromRules`' `rules.timeZone`, or `fromSessions(list, { timeZone })`) + query API: `.sessions()`, `.sessionOn()`, `.isTradingDay()`, `.isOpen()`, `.sessionsInRange()`, `.sessionSequence()`, `.barSequence(period)`, `.tagSessions()`, `.discontinuities()` | `packages/financial/src/calendar/` |
628
+ | `generateSessions` | `Session[]` from `SessionRules` over a date range (DST-correct) | `packages/financial/src/calendar/` |
629
+ | `normalizeSessions` | Validate + sort an explicit session list | `packages/financial/src/calendar/` |
630
+ | `identityDiscontinuity` / `segmentDiscontinuity` / `weekendSkip` | `DiscontinuityProvider`s for the trading-time axis | `packages/financial/src/calendar/` |
631
+ | Types | `Session`, `SessionBreak`, `SessionRules`, `DateRange`, `InstantRange`, `TaggedSchema`, `LiveSegment`, `DiscontinuityProvider` | `packages/financial/src/calendar/` |
632
+ | `SessionSource` / `SessionAnchorOptions` | The session-anchored studies' input: `TradingCalendar \| Session[]` (`sessions`), or a session-id column name (`session`), plus `stamped` | `packages/financial/src/contract/session-anchor.ts` |
633
+ | `PIVOT_METHODS` / `PivotMethod` | The four pivot formula sets (`standard`, `fibonacci`, `woodie`, `camarilla`) | `packages/financial/src/kernels/pivot.ts` |
634
+ | `PivotPointsSchema` / `CamarillaPivotPointsSchema` / `PivotPointsResult` | `pivotPoints`' method-dependent appended-schema types (7 columns, or 9 for Camarilla) | `packages/financial/src/studies/pivot-points.ts` |
630
635
 
631
636
  ### Contract & constants
632
637
 
package/CHANGELOG.md CHANGED
@@ -8,7 +8,9 @@ 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.67.0...HEAD
11
+ [Unreleased]: https://github.com/pond-ts/pond/compare/v0.69.0...HEAD
12
+ [0.69.0]: https://github.com/pond-ts/pond/compare/v0.68.0...v0.69.0
13
+ [0.68.0]: https://github.com/pond-ts/pond/compare/v0.67.0...v0.68.0
12
14
  [0.67.0]: https://github.com/pond-ts/pond/compare/v0.66.0...v0.67.0
13
15
  [0.66.0]: https://github.com/pond-ts/pond/compare/v0.65.0...v0.66.0
14
16
  [0.65.0]: https://github.com/pond-ts/pond/compare/v0.64.0...v0.65.0
@@ -70,6 +72,112 @@ include new features and type-level changes; patch bumps are strictly additive.
70
72
 
71
73
  ## [Unreleased]
72
74
 
75
+ ## [0.69.0] — 2026-09-13
76
+
77
+ ### Added
78
+
79
+ - **`<ChartContainer timeZone>` — the time axis in any IANA zone
80
+ ([PND-TZAXIS]).** Day / week / month ticks land on that zone's midnights,
81
+ Mondays and month starts; labels, the stacked date bands, the hierarchical
82
+ grid, session dividers and every cursor / marker / annotation readout read
83
+ in it. **Omitted ⇒ the viewer's zone**, exactly as before. The d3 specifier
84
+ strings on `timeFormat` / `cursorFormat` are unchanged; `%Z` / `%z` now read
85
+ the zone's abbreviation / offset. Sub-day ticks align to the zone's wall
86
+ clock, so a 6 h grain reads 00 / 06 / 12 / 18 across a DST jump instead of
87
+ drifting by an hour until the next midnight. The resolved zone is on the
88
+ chart context as `timeZone`. Built on core's `TimeZone` ([PND-TZCAL]), so a
89
+ `Sequence.calendar('day', { timeZone })` bucket edge and the tick that
90
+ labels it are one instant — pinned by a cross-package test.
91
+ - **`<XAxis timeZone>` — a second strip in another zone.** Two time axes
92
+ over one shared mapping, each ticking and labelling (and pilling) in its
93
+ own zone: `<XAxis side="top" timeZone="America/New_York" />` above a
94
+ UTC container's own strip below. Backed by
95
+ `TradingTimeScale.withTimeZone(zone)` / `.timeZone()` and an optional
96
+ `DiscontinuityProvider.withTimeZone` (the identity provider re-derives its
97
+ day anchors; a trading calendar's session opens are zone-independent).
98
+ - **`TradingCalendarLike.timeZone?`** — a calendar that carries its exchange
99
+ zone supplies the axis default (`calendar={cal}` renders in exchange time
100
+ wherever it is viewed); an explicit `timeZone` prop wins.
101
+ - `scaleTradingTime(provider, { timeZone })` and
102
+ `identityProvider({ timeZone })` take the zone directly for consumers
103
+ building the scale themselves; `identityProvider`, `TradingCalendarLike`
104
+ and `ScaleTimeZoneOptions` are now exported. Internally the tick ladder
105
+ runs on a `TickCalendar` seam whose local implementation is the previous
106
+ `Date` arithmetic verbatim — the default path is unchanged.
107
+ - **`TradingCalendar.timeZone` ([PND-TZFIN]).** `@pond-ts/financial`'s
108
+ calendar keeps the zone its sessions were resolved in — `fromRules` carries
109
+ `rules.timeZone`, `fromSessions(list, { timeZone })` takes it — so
110
+ `<ChartContainer calendar={cal}>` renders the axis in exchange time with no
111
+ further wiring.
112
+ - `@pond-ts/charts` now depends on `d3-time-format` directly (it was already
113
+ a transitive dependency via `d3-scale`).
114
+ - **`TimeZone` — the zone-calendar primitive ([PND-TZCAL]).** `pond-ts`
115
+ exports `TimeZone.of(id)` (interned; also `TimeZone.UTC`,
116
+ `TimeZone.local()`) with `startOf(unit, t)`, `next(unit, t)`, `parts(t)`,
117
+ `instant(parts, { disambiguation })`, `offsetAt(t)` and
118
+ `abbreviation(t, { locale })`. Temporal underneath, but each zone caches its
119
+ offset transitions as it discovers them, so steady-state calls are integer
120
+ arithmetic: `startOf('day')` went from ~24 µs to ~23 ns per call, and a
121
+ three-year hourly series aggregated to `America/New_York` days from 38 ms
122
+ to 0.5 ms. `Sequence.calendar`, `TimeRange.fromCalendar` and
123
+ `Interval.fromCalendar` now bucket through it (no behaviour change; pinned
124
+ against Temporal on eight zones including southern-hemisphere DST, a
125
+ 30-minute DST shift, a +05:30 zone, a day with no midnight and Samoa's
126
+ skipped day). This is the primitive the charts' time axis will place and
127
+ label ticks with, so a bucket edge and the tick that labels it are one
128
+ instant. First task of the time-zone plan
129
+ (`docs/plans/PND_TIMEZONE_PLAN.md`).
130
+ - **`CalendarUnit` gains `'quarter'` and `'year'`** for
131
+ `Sequence.calendar`, `TimeRange.fromCalendar` and `Interval.fromCalendar`.
132
+
133
+ ### Changed
134
+
135
+ - **`Sequence.calendar` validates its inputs at construction.** An unknown
136
+ unit (`'hour'`) or zone (`'Nowhere'`) now throws `RangeError` immediately;
137
+ before, an unknown unit silently produced wrong buckets (the two unit
138
+ dispatchers fell through to different defaults — the 2026-06 audit's §6
139
+ finding) and an unknown zone failed only on first `bounded()`.
140
+
141
+ ### Fixed
142
+
143
+ - **`pond-ts`: two type-level corners of [PND-PARTCOL] (0.68.0) found by the Codex pass on #724.** (1) On a broad `TimeSeries<SeriesSchema>` with a _literal_ partition column, the injected `'first'` could not look up a kind and typed the column as `undefined`; `WithPartitionColumns` now takes the schema and leaves the mapping alone when the schema is broad, so the result type is exactly 0.67's. (2) `By` had no variance pin, so `PartitionedTimeSeries<S, K, 'host'>` accepted a view partitioned by `region` (and an untyped view could be narrowed to any column); a phantom contravariant member now rejects both while a specialised view still assigns to the legacy `PartitionedTimeSeries<S>` shape. Type tests cover both plus the `K`-survives-`smooth`/`baseline` claim. No runtime change.
144
+ - **Docs said a wall-clock string without `parse.timeZone` throws. It never
145
+ did** ([PND-TZDOCS]) — it is read as UTC, silently. `creating.mdx`, the
146
+ agent guide (`AGENTS.md`) and the decision table now say so and describe
147
+ how the shift shows up. The agent guide also gains the one time-zone rule:
148
+ pass the same `timeZone` to `Sequence.calendar` and `<ChartContainer>`.
149
+ The aggregation page cross-links `Sequence.calendar` for weekly / monthly
150
+ bars (issue #358 item 1, supersedes #359). The finance gallery's off-chart
151
+ readout takes the calendar's zone instead of hard-coding New York; the
152
+ Niño 3.4 heat map's year grain uses `Sequence.calendar('year')`.
153
+
154
+ ## [0.68.0] — 2026-09-13
155
+
156
+ ### Added
157
+
158
+ - **Agent adoption tranche ([PND-ADOPTMETA] / [PND-ADOPTLINKS] /
159
+ [PND-LLMSTXT] / [PND-AGENTGUIDE] / [PND-SKILL] / [PND-CONTEXT7]).** Every
160
+ package now declares `keywords`, `homepage` and `bugs` (there were none —
161
+ `pond-ts` ranked last in `npm search "time series"`). Every tarball ships an
162
+ `AGENTS.md` (source `docs/agents/USING_POND.md`): which package for which
163
+ task, the core idioms, the mistakes agents make. `pond-ts.org/llms.txt` is
164
+ now llmstxt.org-shaped (titles + descriptions per page, one section per
165
+ docs area, `Optional` links to `API.md` / the agent guide) with per-area
166
+ `llms-<area>.txt` dumps so a single fetch stays small. A Claude Code plugin
167
+ marketplace lives in the repo (`/plugin marketplace add pond-ts/pond`) with
168
+ `pond-ts`, `pond-charts` and `pond-financial` skills. `context7.json`
169
+ configures docs-MCP indexing. Plan and baseline:
170
+ `docs/plans/PND_ADOPTION_PLAN.md`.
171
+
172
+ - **Agent guide + skill hardened by the first cold-start run** (`docs/agents/USING_POND.md`, shipped as `AGENTS.md`; `plugins/pond-ts/skills/pond-ts`): install with `@latest` and the `.d.ts` paths that carry signatures. Cold-start harness committed at `docs/adoption/cold-start/`.
173
+
174
+ ### Fixed
175
+
176
+ - **`pond-ts`: the partition column is now in the static type after a partitioned `aggregate` / `rolling` ([PND-PARTCOL]).** `series.partitionBy('host').aggregate(seq, { p95: { from: 'ms', using: 'p95' } }).collect()` always carried `host` at runtime (auto-injected as `'first'`) but the result type omitted it, so `e.get('host')` failed to compile — every fresh agent in the cold-start experiment hit or pre-empted it. `PartitionedTimeSeries` gains a third type parameter `By` (the partition column names, captured by `partitionBy`, default `never`), and the two schema-replacing operators are typed over `WithPartitionColumns<Mapping, By>` — the user's keys win, kind and all; missing partition columns are added as `'first'`. Composite partitions and typed `groups` carry through; `smooth` / `baseline` under `partitionBy` now also keep `K`. Additive: untyped views are unchanged.
177
+ - `@pond-ts/charts` and `@pond-ts/fit` READMEs (rendered on npm) and eight
178
+ docs pages pointed at the retired `pjm17971.github.io/pond-ts` site /
179
+ `pjm17971/pond-ts` repo; now `pond-ts.org` / `pond-ts/pond`.
180
+
73
181
  ## [0.67.0] — 2026-09-11
74
182
 
75
183
  ### Added
package/README.md CHANGED
@@ -1,21 +1,44 @@
1
1
  # pond-ts
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/pond-ts?label=pond-ts)](https://www.npmjs.com/package/pond-ts)
4
+ [![CI](https://github.com/pond-ts/pond/actions/workflows/ci.yml/badge.svg)](https://github.com/pond-ts/pond/actions/workflows/ci.yml)
5
+ [![license: MIT](https://img.shields.io/npm/l/pond-ts)](https://github.com/pond-ts/pond/blob/main/LICENSE)
6
+ [![docs](https://img.shields.io/badge/docs-pond--ts.org-1f6feb)](https://pond-ts.org)
7
+
3
8
  **Highly optimised, fully typed Timeseries library for TypeScript**
4
9
 
5
10
  Schema-driven events, composable batch transforms, push-based streaming
6
- ingest, multi-entity partitioning, and an optional React integration
7
- all strict TypeScript end to end, all immutable.
11
+ ingest, multi-entity partitioning and, optionally, React hooks and
12
+ canvas charts that read the series directly. All strict TypeScript end to
13
+ end, all immutable.
8
14
 
9
15
  **pond-ts** is the TypeScript-first successor to
10
16
  [pondjs](https://github.com/esnet/pond), rewritten from scratch with a
11
17
  focus on type safety, composability, and the live-streaming patterns
12
18
  that pondjs never grew.
13
19
 
20
+ ## The packages
21
+
22
+ Three packages carry most projects. The core has no dependency on the other
23
+ two; add them only if you render.
24
+
25
+ | Package | What it is | Needs |
26
+ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
27
+ | **[`pond-ts`](https://www.npmjs.com/package/pond-ts)** — core | `TimeSeries` (batch) and `LiveSeries` (streaming) with one operator vocabulary: aggregate, rolling, align, fill, partition, join, typed columns. Node or browser, no React. | nothing |
28
+ | **[`@pond-ts/charts`](https://www.npmjs.com/package/@pond-ts/charts)** — optional | Declarative React charts on a canvas data plane that consume a pond series with no adapter: line, area, band, bar, scatter, box, candlestick, heat map; cursors, selection, pan/zoom, annotations. | `pond-ts`, `@pond-ts/react`, React 18/19 |
29
+ | **[`@pond-ts/react`](https://www.npmjs.com/package/@pond-ts/react)** — optional | Hooks to own a series in a component and read live views on a throttled snapshot cadence (`useLiveSeries`, `useSnapshot`, …). | `pond-ts`, React 18/19 |
30
+
14
31
  ```sh
15
- npm install pond-ts # core
16
- npm install @pond-ts/react # React hooks (optional)
32
+ npm install pond-ts # core — enough for Node pipelines and non-React apps
33
+ npm install @pond-ts/charts @pond-ts/react pond-ts # add the React chart stack
17
34
  ```
18
35
 
36
+ Two domain packages ([`@pond-ts/financial`](#domain-packages) for markets,
37
+ [`@pond-ts/fit`](#domain-packages) for activity data) and one experimental
38
+ runtime ([`@pond-ts/process`](#domain-packages)) sit on top — see
39
+ [Domain packages](#domain-packages) below. All six release together under
40
+ one version; keep them in step.
41
+
19
42
  - **Typed schemas** — declare once, every transform downstream narrows
20
43
  off it. `event.get('cpu')` returns `number | undefined` straight from
21
44
  the schema; no `as` casts.
@@ -108,6 +131,43 @@ The full live surface (`filter`, `map`, `select`, `window`, `aggregate`,
108
131
  `sample`) is incremental — events flow, views emit, retention bounds
109
132
  memory.
110
133
 
134
+ ## Quick start: charts (React)
135
+
136
+ `@pond-ts/charts` reads a `TimeSeries` or `LiveSeries` directly — do the maths
137
+ in pond, hand the result to a layer. Rows share one x scale, so they pan,
138
+ zoom and track the cursor together.
139
+
140
+ ```tsx
141
+ import {
142
+ BandChart,
143
+ ChartContainer,
144
+ ChartRow,
145
+ Layers,
146
+ LineChart,
147
+ YAxis,
148
+ } from '@pond-ts/charts';
149
+
150
+ // `bands` is the baseline() result from the batch quick start:
151
+ // cpu + avg / sd / upper / lower columns.
152
+ export function CpuChart({ width }: { width: number }) {
153
+ return (
154
+ <ChartContainer width={width} cursor="crosshair" panZoom>
155
+ <ChartRow height={240}>
156
+ <YAxis id="cpu" format=".0%" />
157
+ <Layers>
158
+ <BandChart series={bands} lower="lower" upper="upper" axis="cpu" />
159
+ <LineChart series={bands} column="cpu" axis="cpu" />
160
+ </Layers>
161
+ </ChartRow>
162
+ </ChartContainer>
163
+ );
164
+ }
165
+ ```
166
+
167
+ Pass `width="auto"` to measure the parent instead. Live data renders through
168
+ the same layers: own the series with `useLiveSeries` from `@pond-ts/react`
169
+ and pass its snapshot as `series`.
170
+
111
171
  ## Quick start: multi-entity
112
172
 
113
173
  `partitionBy` routes events into per-key buffers. Every stateful
@@ -182,6 +242,26 @@ it is behind. Run locally:
182
242
  npm run build && node packages/core/bench/vs-pondjs.cjs
183
243
  ```
184
244
 
245
+ ## Domain packages
246
+
247
+ Optional, domain-specific, all on plain pond series:
248
+
249
+ - **[`@pond-ts/financial`](https://www.npmjs.com/package/@pond-ts/financial)**
250
+ — sixty-plus oracle-verified technical studies (SMA, EMA, RSI, MACD,
251
+ Bollinger, ATR, VWAP, …) that append columns to a bar series, a fluent
252
+ `bars.sma({ period: 20 }).rsi({ period: 14 })` form, and a
253
+ `TradingCalendar` so session-aligned bars, rolling windows and chart axes
254
+ stop at the close.
255
+ - **[`@pond-ts/fit`](https://www.npmjs.com/package/@pond-ts/fit)** — fitness
256
+ and activity analytics: typed quantities with units, canonical activity
257
+ series, geo (distance, elevation, best efforts), power (NP / IF / TSS,
258
+ curves), heart-rate zones, splits.
259
+ - **[`@pond-ts/process`](https://www.npmjs.com/package/@pond-ts/process)** —
260
+ **experimental.** Computations as data: processing graphs authored fluently
261
+ or composed as JSON, resolved against a declared op vocabulary with
262
+ content-addressed caching, provenance and per-node timings. The API is
263
+ expected to move.
264
+
185
265
  ## Documentation
186
266
 
187
267
  The full guide is at **<https://pond-ts.org/>**.
@@ -202,6 +282,26 @@ The full guide is at **<https://pond-ts.org/>**.
202
282
  — TypeDoc output, every public class and method.
203
283
  - **[CHANGELOG](./CHANGELOG.md)** — what shipped in each release.
204
284
 
285
+ ## For coding agents
286
+
287
+ pond is built by agents and expects to be used by them. Three things exist so
288
+ an agent can go from "never heard of pond" to working code without a human in
289
+ the loop:
290
+
291
+ - **`AGENTS.md` + `API.md` ship inside every npm tarball** —
292
+ `node_modules/pond-ts/AGENTS.md` is a one-read guide (which package for
293
+ which task, the idioms, the mistakes agents make); `API.md` maps every
294
+ public export to its source file. Source:
295
+ [docs/agents/USING_POND.md](docs/agents/USING_POND.md), [API.md](API.md).
296
+ - **<https://pond-ts.org/llms.txt>** — every docs page with a one-line
297
+ description, plus `llms-<area>.txt` single-fetch dumps per package.
298
+ - **Claude Code plugin** — skills for core, charts and financial, versioned
299
+ with the library:
300
+ ```
301
+ /plugin marketplace add pond-ts/pond
302
+ /plugin install pond-ts@pond-ts
303
+ ```
304
+
205
305
  ## Examples
206
306
 
207
307
  - **[pond-ts-dashboard](https://github.com/pjm17971/pond-ts-dashboard)**
@@ -213,20 +313,22 @@ The full guide is at **<https://pond-ts.org/>**.
213
313
 
214
314
  ## Develop
215
315
 
216
- The repo is an npm-workspaces monorepo with two published packages
217
- (`pond-ts`, `@pond-ts/react`). Node 18+ for runtime; Node 20+ for the
316
+ The repo is an npm-workspaces monorepo with six published packages
317
+ (`pond-ts`, `@pond-ts/react`, `@pond-ts/charts`, `@pond-ts/financial`,
318
+ `@pond-ts/fit`, `@pond-ts/process`). Node 18+ for runtime; Node 20+ for the
218
319
  docs site (Docusaurus).
219
320
 
220
321
  ```sh
221
- npm install # one-time, hoists deps for both packages
322
+ npm install # one-time, hoists deps for all packages
222
323
  npm run build # build both packages
223
324
  npm test # runtime + type-level tests on both packages
224
325
  npm run format # prettier write across the repo
225
326
  npm run verify # format check + build + test (CI parity)
226
327
  ```
227
328
 
228
- `packages/core/` is the `pond-ts` package; `packages/react/` is
229
- `@pond-ts/react`. Docs live in `website/`.
329
+ Each package lives under `packages/<name>/` (`core` is `pond-ts`, the rest
330
+ match their scoped names). Docs live in `website/` — its own npm root, not a
331
+ workspace.
230
332
 
231
333
  ## License
232
334
 
package/package.json CHANGED
@@ -1,7 +1,24 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.67.0",
4
- "description": "React hooks for pond-ts live time series",
3
+ "version": "0.69.0",
4
+ "description": "React hooks for pond-ts: subscribe to LiveSeries and derived views with throttled snapshots",
5
+ "keywords": [
6
+ "time-series",
7
+ "timeseries",
8
+ "typescript",
9
+ "streaming",
10
+ "analytics",
11
+ "react",
12
+ "hooks",
13
+ "live",
14
+ "realtime",
15
+ "dashboard",
16
+ "pond-ts"
17
+ ],
18
+ "homepage": "https://pond-ts.org/docs/react/",
19
+ "bugs": {
20
+ "url": "https://github.com/pond-ts/pond/issues"
21
+ },
5
22
  "license": "MIT",
6
23
  "repository": {
7
24
  "type": "git",
@@ -24,17 +41,18 @@
24
41
  "files": [
25
42
  "dist",
26
43
  "CHANGELOG.md",
27
- "API.md"
44
+ "API.md",
45
+ "AGENTS.md"
28
46
  ],
29
47
  "scripts": {
30
48
  "build": "tsc -p tsconfig.json",
31
- "prepack": "cp ../../README.md ./README.md && cp ../../LICENSE ./LICENSE && cp ../../CHANGELOG.md ./CHANGELOG.md && cp ../../API.md ./API.md && npm run build && cp cjs-fallback.cjs dist/cjs-fallback.cjs && find dist -name '*.map' -delete",
49
+ "prepack": "cp ../../README.md ./README.md && cp ../../LICENSE ./LICENSE && cp ../../CHANGELOG.md ./CHANGELOG.md && cp ../../API.md ./API.md && cp ../../docs/agents/USING_POND.md ./AGENTS.md && npm run build && cp cjs-fallback.cjs dist/cjs-fallback.cjs && find dist -name '*.map' -delete",
32
50
  "test": "npm run test:type && npm run test:runtime",
33
51
  "test:type": "tsc -p tsconfig.types.json",
34
52
  "test:runtime": "vitest run"
35
53
  },
36
54
  "peerDependencies": {
37
- "pond-ts": "^0.67.0",
55
+ "pond-ts": "^0.69.0",
38
56
  "react": "^18.0.0 || ^19.0.0"
39
57
  },
40
58
  "devDependencies": {