# Changelog All notable changes to `chart-realtime` documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from `1.0.0` onward. Pre-`1.0.0` releases share the `0.5.0-SNAPSHOT` Maven coordinate per `group:dev.dtrentin`, `artifact:chart-realtime`. Stable coordinates and Maven Central publishing land in `1.0.0`. ## [Unreleased] ### Changed - KGP 2.3 / AGP 9 deprecation warnings cleanup (in progress) ## [0.5.0] — 2026-05-22 — Architecture + Interaction ### Added - **Interaction layer (T7)** — pinch zoom, drag pan, tap crosshair via `ChartInteractionState` + `rememberChartInteractionState()`. `ViewportMode` sealed (`Following` / `Frozen` / `History(anchorMs)`). Swipe-to-right-edge resumes `Following`. `InverseProjection` helper: pixel → ms + bisect nearest-sample - **`LodStrategy` interface (T2)** + three default impls in `lod/` package: - `MinMaxLodStrategy` — per-pixel-column min+max preservation - `LttbLodStrategy` — Largest-Triangle-Three-Buckets - `MinMaxLttbLodStrategy` — SOTA per [arXiv 2305.00332](https://arxiv.org/abs/2305.00332), 1.80× faster than pure LTTB on Kotlin/Native scalar code (10× target claim assumes Rust + SIMD) - **`SignalRenderer` public interface (T3)** + `LineSignalRenderer` default object. Custom renderer impl-able in ~30 LOC. Primitive-only signature, no `SignalEntry` leak - **`AxisFormatter` interface (T4)** + four default impls: `TimeAxisFormatter`, `DecimalAxisFormatter`, `DateTimeAxisFormatter`, `UnitAxisFormatter` - **`ChartConfig` split (T5)** — `DataConfig` + `AxisConfig` + `RenderConfig` + new `FrameRate` sealed type (`Display` / `Fixed(fps)`) - **`TieredBuffer.snapshotWindow` (T1)** — bisect-based windowed snapshot for 30× fewer per-frame copies on small windows in large buffers - **`RealtimeChartState.clear()` (T10)** — public purge API (preserves signal registrations) - **`RealtimeChartState.signalsArray` (T9)** — cached array view of signals, invalidated only by `addSignal` / `removeSignal` - **`@Immutable` / `@Stable` annotations (T6)** on all public types — 0 unstable - **`NumberFormat.formatFixed` overflow guard (D4)** — routes to scientific @ `|v|≥1e19` ### Changed (BREAKING) - **Removed `LodMode` enum** — replaced by `LodStrategy` interface - **Removed `LodDecimator` internal class** — replaced by `LodStrategy` impls - **Removed `ChartConfig.targetFps` field** — replaced by `RenderConfig.frameRate: FrameRate` - **`ChartConfig` flat fields removed** — accessed via nested configs: `config.data.xWindowSeconds`, `config.render.theme`, `config.axis.xLabelMode`, etc. Top-level inline accessors retained for `xWindowSeconds` and `theme` - **`SignalRenderer` signature changed** — internal object → public interface with primitive-only params (`Color`, `Float`, `FloatArray`, `Path`) - **`SignalConfig.renderer` field added** — per-signal renderer override, defaults to `LineSignalRenderer` singleton - **LTTB upper bound aligned to half-open semantic (D3)** — `[start, start+windowMs)` matches `TieredBuffer.snapshot`. Samples at exact upper bound now excluded - **`Pair` removed from `AxisRenderer.resolveYRange`** — replaced by `FloatArray` out param ### Performance - LineSignalRenderer caches `Stroke` instances per strokeWidth (T8) - AxisRenderer caches `TextStyle` instances (16-entry cap) - Render path: 0 `Pair`/`Map`/`Iterator` allocations per frame at steady state ### Toolchain - Kotlin 2.1.0 → 2.3.21 - Compose Multiplatform 1.8.0 → 1.11.0 - Android Gradle Plugin 8.7.3 → 9.2.0 - Gradle 8.11.1 → 9.5.1 - coroutines 1.9.0 → 1.11.0; kotlin-test → 2.3.21 - **Dropped `kotlinx-datetime`** — replaced by stdlib `kotlin.time.Clock` (Kotlin 2.3 stable) - **Dropped `iosX64` target** — Compose Multiplatform 1.11.0 no longer ships `ios_x64` variants (Intel Mac iOS simulators deprecated by Apple since Xcode 15+) ### Tests - 107 → **207** (+100). Coverage: `lod/` (24), `interaction/` (29), `model/ChartConfigTest` (8), `render/AxisFormatterTest` (10), `RealtimeChartStateTest` (+9), `NumberFormatTest` (+6), `TieredBufferTest` (+10), `CircularBufferTest` (+10), `AxisRendererTest` (3), `LineSignalRendererTest` (2) ### API surface - `chart-realtime.api`: 161 → 428 LOC - `chart-realtime.klib.api`: 211 → 501 LOC ## [0.4.0] — 2026-05-21 — Portfolio Hardening 15 tasks shipped via 3-reviewer audit (android-reviewer + mobile-performance-reviewer + mobile-architect). Library moved from "prototype" to "senior-engineer-built KMP library". ### Added - LICENSE MIT - `explicitApi() Strict` + Binary Compatibility Validator (BCV) 0.16.3 + klib enabled - ABI baseline locked (`chart-realtime.api` 161 LOC, `chart-realtime.klib.api` 211 LOC) - `hasData` boolean flag (replaces `POSITIVE_INFINITY` / `Float.MAX_VALUE` sentinels) - `Snapshot.withMutableSnapshot` + `SnapshotApplyConflictException` retry helper - M4 binning for Tier1/Tier2 (firstTs/V, minTs/V, maxTs/V, lastTs/V) with chronological dedup - NaN/Inf/backward-timestamp guards on `push`; per-signal `lastPushedTs` tracking - NumberFormat extracted from AxisRenderer (38 tests) - 25 buffer/LoD tests (tier roll, window crossing, clear, LTTB branches) ### Changed - `CircularBuffer.writeIndex` `Int` → `Long` (fixes 124d overflow @ 200Hz) - `kotlinx-datetime` 0.6.2 replaces `expect/actual currentTimeMs` (Platform.kt deleted) - `dataVersion` → `mutableLongStateOf` (Compose-observable, idle = 0 recompositions) - Single `buffer.snapshot()` per signal per frame via per-signal scratch arrays - `TIER1_CAPACITY` 12k → 24k, `TIER2_CAPACITY` 5.4k → 10.8k (×2 → ×4 for M4 binning) - Memory per signal: ~760 KB → ~1.14 MB ### Removed - `RenderLoop.kt`, `rememberFrameTick` (deleted) - `maxBufferSeconds`, `xLabelDecimals`, `SignalConfig.label` (dead config) - All `!!` and `@Suppress("UNUSED_EXPRESSION")` from commonMain ### Internal lockdown - `CircularBuffer`, `TieredBuffer`, `LodDecimator`, `dataVersion`, `rememberFrameTick`, capacity constants ### Tests - 33 → 107 (+74) ## [0.3.0] — 2026-05-21 — iOS Build Pipeline + Dirty Flag Render ### Added - iOS targets compile + link green: `iosX64`, `iosArm64`, `iosSimulatorArm64` - `applyDefaultHierarchyTemplate()` auto-wires `iosMain` / `iosTest` - XCFramework `ChartRealtime` (debug + release): `ios-arm64` + `ios-arm64_x86_64-simulator` - Dirty-flag render: `dataVersion` counter, skip Canvas draw when no new data - `targetFps = 30` default in `ChartConfig` (null = max display refresh rate) - iOS-side `Platform.ios.kt`: `NSDate()` + `timeIntervalSinceReferenceDate` + 1970 epoch offset (978_307_200s) - KDoc on `RealtimeChart`, `RealtimeChartState`, `ChartConfig`, `LodMode`, `AxisLabelMode` - README sections: LoD, AxisLabels, Material3 ### Fixed - Multiplatform replacements for JVM-only `String.format`: `formatFixed` / `formatScientific` / `pow10` helpers in `AxisRenderer` - `kotlin.concurrent.Volatile` + `kotlin.jvm.JvmName` imports (OptionalExpectation) - X axis: show negative values instead of clamping to `"0s"` ### Tests - 17 → 33 (`RealtimeChartStateTest`: dataVersion + signal lifecycle) ## [0.2.0] — 2026-05-21 — Polish ### Added - `TieredBuffer`: 3-tier ring buffer (5min full-rate, 10min @ 10Hz, 45min @ 1Hz), ~5MB footprint for 1h of data - `LodMode.LTTB` (Largest-Triangle-Three-Buckets, O(n) decimation) - `AxisLabelMode` (`INSIDE` / `BESIDE` / `HIDDEN`) with `chartLeft` / `chartBottom` insets - `T0`-relative X-axis timestamps with mm:ss / hh:mm:ss formatting (negative supported) - Material3 dynamic theming (light/dark, wallpaper-based, API 31+) - Camera cutout / edge-to-edge support - Sensor domain layer (Clean Architecture) - Koin 4.0.0 DI wiring - Sensor rate cap: 200Hz (5_000μs) ### Changed - Package rename: `eu.henesis` → `dev.dtrentin` - `TieredBuffer` binning: `MEAN` → `MIN_MAX` (composable across tier boundaries, no synthetic values, no C7 spike artifacts) - `LodDecimator`: pre-allocated scratch arrays (zero GC at render time) ### Removed - `LodMode.MEAN` (introduced synthetic values absent in raw signal) ## [0.1.0] — 2026-05-20 — MVP Initial implementation. Six phases shipped: 1. Module scaffold (KMP + Compose Multiplatform + Android library) 2. `CircularBuffer` + `LodDecimator` 3. `RealtimeChartState` API 4. `RealtimeChart` composable + Canvas-based `SignalRenderer` + `AxisRenderer` 5. Demo app (`:app`) 6. Polish + initial docs [Unreleased]: https://github.com/davide-trentin/KMPCharts/compare/v0.5.0...HEAD [0.5.0]: https://github.com/davide-trentin/KMPCharts/releases/tag/v0.5.0 [0.4.0]: https://github.com/davide-trentin/KMPCharts/releases/tag/v0.4.0 [0.3.0]: https://github.com/davide-trentin/KMPCharts/releases/tag/v0.3.0 [0.2.0]: https://github.com/davide-trentin/KMPCharts/releases/tag/v0.2.0 [0.1.0]: https://github.com/davide-trentin/KMPCharts/releases/tag/v0.1.0