Ships v0.5.0 via kmp-manager 6-phase flow. 13 plan tasks (D1, T1-T10, D3, D4), 14 agent calls, 6 P-groups. 107 → 207 tests on iosSimulatorArm64. Toolchain (D1): - 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 - Drop kotlinx-datetime; use stdlib kotlin.time.Clock - Drop iosX64 target (Compose-MP 1.11.0 has no ios_x64 variant) Architecture (T1-T6, T10): - TieredBuffer.snapshotWindow: bisect-based windowed snapshot - New lod/ package: LodStrategy interface + MinMax/Lttb/MinMaxLttb impls (MinMaxLttb SOTA per arXiv 2305.00332, 1.80× faster than pure LTTB) - New render/SignalRenderer: public interface + LineSignalRenderer object - New render/AxisFormatter: 4 default impls (Time, Decimal, DateTime, Unit) - HARD BREAK: deleted LodMode, LodDecimator, ChartConfig.targetFps - ChartConfig split: DataConfig + AxisConfig + RenderConfig + FrameRate sealed - @Immutable/@Stable on all public types (0 unstable) - RealtimeChartState.clear() API Interaction layer (T7): - New interaction/ package - ChartInteractionState + rememberChartInteractionState() - ViewportMode sealed: Following / Frozen / History(anchorMs) - Pinch zoom + drag pan + tap crosshair gestures - Swipe-to-edge resumes Following - InverseProjection: pixel → ms + bisect nearest-sample Perf finishing (T8, T9, D3): - LineSignalRenderer Stroke cache, AxisRenderer TextStyle cache - resolveYRange Pair<Float,Float> → FloatArray out-param - RealtimeChartState.signalsArray cached (invalidated on add/remove only) - LTTB upper-bound aligned to half-open [start, start+windowMs) semantic Correctness (D4): - NumberFormat.formatFixed Long overflow guard @ |v|≥1e19 ABI baseline regenerated: - chart-realtime.api: 161 → 428 LOC - chart-realtime.klib.api: 211 → 501 LOC Modules touched: chart-realtime (lib), app (consumer), gradle (toolchain), .paul (state). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
224 lines
9.3 KiB
Markdown
224 lines
9.3 KiB
Markdown
# Roadmap: chart-realtime
|
||
|
||
## Overview
|
||
|
||
KMP real-time chart library. Six phases v0.1.0 MVP shipped; v0.2.0 polish shipped.
|
||
|
||
## Milestones
|
||
|
||
| Milestone | Version | Status | Completed |
|
||
|-----------|---------|--------|-----------|
|
||
| v0.1.0 MVP | 0.1.0 | ✓ Shipped | 2026-05-20 |
|
||
| v0.2.0 Polish | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
||
| v0.3.0 iOS Build Pipeline | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
||
| v0.4.0 Portfolio Hardening | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
||
| v0.5.0 Architecture + Interaction | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
||
| v1.0.0 Public Release | - | Planned | - |
|
||
|
||
---
|
||
|
||
## v0.1.0 — MVP (SHIPPED)
|
||
|
||
Status: ✓ Complete
|
||
Phases: 6 of 6 complete
|
||
|
||
### Phases
|
||
|
||
| Phase | Name | Status | Completed |
|
||
|-------|------|--------|-----------|
|
||
| 1 | Module Scaffold | ✓ Done | 2026-05-20 |
|
||
| 2 | Buffer + LoD | ✓ Done | 2026-05-20 |
|
||
| 3 | State API | ✓ Done | 2026-05-20 |
|
||
| 4 | Render Engine | ✓ Done | 2026-05-20 |
|
||
| 5 | Demo App | ✓ Done | 2026-05-20 |
|
||
| 6 | Polish + Docs | ✓ Done | 2026-05-20 |
|
||
|
||
Delivered: CircularBuffer, LodDecimator, RealtimeChart composable, basic AxisRenderer.
|
||
|
||
---
|
||
|
||
## v0.2.0 — Polish (SHIPPED as v0.5.0-SNAPSHOT)
|
||
|
||
Status: ✓ Complete
|
||
Summary: .paul/phases/v0.2.0-plan-SUMMARY.md
|
||
|
||
### Delivered
|
||
|
||
- TieredBuffer (3-tier 1h ring buffer, MIN_MAX binning, zero-alloc)
|
||
- LodMode enum (MIN_MAX, LTTB) — MEAN removed
|
||
- LTTB render-time decimation (O(n) Largest-Triangle-Three-Buckets)
|
||
- AxisLabelMode (INSIDE/BESIDE/HIDDEN) with chartLeft/chartBottom insets
|
||
- T0-relative X-axis timestamps with mm:ss/hh:mm:ss formatting, negative support
|
||
- 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
|
||
- Package rename: eu.henesis → dev.dtrentin
|
||
- Git remote: ssh://git@3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git
|
||
- Sensor rate cap: 200Hz (5_000μs)
|
||
|
||
---
|
||
|
||
## v0.3.0 — iOS Build Pipeline (SHIPPED)
|
||
|
||
Status: ✓ Complete
|
||
Plan: .paul/phases/v0.3.0-ios-build-plan.md
|
||
|
||
### Delivered
|
||
|
||
- 3 iOS targets compile green (iosX64, iosArm64, iosSimulatorArm64) — iosX64 dropped in v0.5.0 D1 (Compose-MP 1.11.0 no ios_x64 variant)
|
||
- `applyDefaultHierarchyTemplate()` wires iosMain/iosTest across targets
|
||
- `linkDebugFrameworkIosSimulatorArm64` green
|
||
- `iosSimulatorArm64Test` green — 33 tests, 0 failures
|
||
- XCFramework `ChartRealtime` produced (debug + release): ios-arm64 + ios-arm64_x86_64-simulator slices
|
||
- Pre-existing compile bugs fixed:
|
||
- `@Volatile` → `kotlin.concurrent.Volatile` import
|
||
- `@JvmName` → `kotlin.jvm.JvmName` import (OptionalExpectation)
|
||
- `String.format` in AxisRenderer → manual `formatFixed`/`formatScientific`/`pow10` helpers
|
||
- `NSDate.date()` → `NSDate()` + `timeIntervalSinceReferenceDate` + 1970 epoch offset
|
||
- Android non-regression confirmed (`assembleRelease`, `publishToMavenLocal --dry-run`)
|
||
- Local Mac config: removed Linux JDK pin from `gradle.properties`, added `local.properties` for Android SDK
|
||
|
||
---
|
||
|
||
## v0.4.0 — Portfolio Hardening (SHIPPED)
|
||
|
||
Plan: .paul/phases/v0.4.0-portfolio-hardening-plan.md
|
||
Source: 3-reviewer audit 2026-05-21
|
||
Executed via: kmp-manager group-by-group flow, 8 P-groups, ~15 agent calls
|
||
Completed: 2026-05-21
|
||
|
||
### Delivered
|
||
|
||
**Critical fixes:**
|
||
- LICENSE MIT (T1)
|
||
- explicitApi() Strict + BCV 0.16.3 + klib enabled (T2) — ABI baseline 161/211 LOC
|
||
- Internal lockdown of CircularBuffer / TieredBuffer / LodDecimator / dataVersion / rememberFrameTick (T3)
|
||
- kotlinx-datetime 0.6.2 replaces expect/actual currentTimeMs (T4) — Platform.* 3 files deleted
|
||
- CircularBuffer.writeIndex Int→Long (T5) — overflow 124d @ 200Hz fixed
|
||
- @Suppress + !! removed from commonMain (T6)
|
||
- Dead config removed: maxBufferSeconds, xLabelDecimals, SignalConfig.label (T7)
|
||
|
||
**Correctness:**
|
||
- POSITIVE_INFINITY/Float.MAX_VALUE sentinels → hasData boolean flag (T8)
|
||
- Snapshot.withMutableSnapshot + SnapshotApplyConflictException retry in push/addSignal/removeSignal (T9)
|
||
- dataVersion mutableLongStateOf — Compose-observable, idle = 0 recompositions (T10)
|
||
- RenderLoop.kt deleted; targetFps @Deprecated (T6/T10)
|
||
- Single snapshot per signal per frame via per-signal scratch arrays in SignalEntry (T11)
|
||
- M4 binning Tier1/Tier2: firstTs/V, minTs/V, maxTs/V, lastTs/V with chronological dedup (T12) — no C7 spikes
|
||
- NaN/Inf/backward-timestamp guards on push, KDoc contract, per-signal lastPushedTs (T13)
|
||
|
||
**Test gap fill:**
|
||
- NumberFormat extracted from AxisRenderer to internal object (T14) — 38 tests
|
||
- TieredBufferTest +15 tests (tier roll, window crossing, clear, edge cases) (T15)
|
||
- LodDecimatorTest +10 LTTB-specific tests (T15)
|
||
- iosSimulatorArm64Test: **33 → 107 tests** (target was ≥60)
|
||
|
||
**Capacity changes:**
|
||
- TIER1_CAPACITY 12k → 24k (×2 → ×4)
|
||
- TIER2_CAPACITY 5.4k → 10.8k (×2 → ×4)
|
||
- TOTAL_CAPACITY 77.4k → 94.8k
|
||
- Memory per signal: ~1.14 MB (was ~760 KB pre-bump)
|
||
|
||
**Deviations / deferred to v0.5.0:**
|
||
- kotlinx-datetime 0.6.2 (not 0.7.x) — Kotlin 2.1.0 compat
|
||
- ChartConfig.targetFps @Deprecated but kept for ABI stability
|
||
- NumberFormat Long overflow @ 1e19 (safe via current routing)
|
||
- LTTB upper-bound inclusive vs snapshot half-open semantic mismatch
|
||
|
||
---
|
||
|
||
## v0.5.0 — Architecture + Interaction (SHIPPED)
|
||
|
||
Plan: .paul/phases/v0.5.0-architecture-plan.md (v2 deltas)
|
||
Executed via: kmp-manager 6-phase flow, 6 P-groups, ~14 agent calls
|
||
Completed: 2026-05-21
|
||
|
||
### Delivered
|
||
|
||
**Toolchain (D1):**
|
||
- 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`; swapped to stdlib `kotlin.time.Clock` (Kotlin 2.3 stable)
|
||
- Dropped `iosX64` target (Compose-MP 1.11.0 incompat — Intel Mac sims deprecated)
|
||
|
||
**Architecture (T1-T6, T10):**
|
||
- T1: Bisect tier snapshot (`TieredBuffer.snapshotWindow`) — 26 new tests
|
||
- T2: `LodStrategy` interface + `MinMaxLodStrategy` + `LttbLodStrategy` + `MinMaxLttbLodStrategy` (SOTA, 1.80× faster than pure LTTB on iOS sim; arXiv 2305.00332 ratio=4)
|
||
- T3: `SignalRenderer` interface (public) + `LineSignalRenderer` default impl
|
||
- T4: `AxisFormatter` interface + `TimeAxisFormatter` + `DecimalAxisFormatter` + `DateTimeAxisFormatter` + `UnitAxisFormatter`
|
||
- T5: **HARD BREAK** — deleted `LodMode` enum + `LodDecimator` + `ChartConfig.targetFps`. Split `ChartConfig` → `DataConfig` + `AxisConfig` + `RenderConfig` + `FrameRate` sealed
|
||
- T6: `@Immutable`/`@Stable` annotations — 0 unstable public types
|
||
- T10: `RealtimeChartState.clear()` API
|
||
|
||
**Interaction layer (T7):**
|
||
- `ChartInteractionState` + `rememberChartInteractionState()` hoistable state
|
||
- `ViewportMode` sealed: `Following` / `Frozen` / `History(anchorMs)`
|
||
- `CrosshairState` + `SignalValueAt` (List, not Map — zero boxing)
|
||
- `InteractionConfig` (zoomEnabled/panEnabled/tapCrosshairEnabled + clamps)
|
||
- Pinch zoom + drag pan + tap crosshair + swipe-to-edge-resumes-Following
|
||
- `InverseProjection`: pixel→ms + bisect-based nearest-sample
|
||
|
||
**Perf finishing (T8, T9, D3):**
|
||
- T8: `LineSignalRenderer.Stroke` cache + `AxisRenderer.TextStyle` cache (16-entry cap, single-thread UI)
|
||
- T9: `Pair<Float,Float>` removed (FloatArray out-param) + `signalsArray` cached on `RealtimeChartState` (invalidated by addSignal/removeSignal only)
|
||
- D3: LTTB upper-bound aligned to half-open `[start, start+windowMs)` snapshot semantic
|
||
|
||
**Correctness (D4):**
|
||
- D4: `NumberFormat.formatFixed` Long overflow guard @ |v|≥1e19 → routes to scientific
|
||
|
||
**Test gap fill:**
|
||
- 107 → **207 tests** (+100)
|
||
- New suites: lod/ (24 tests), interaction/ (29 tests), model/ChartConfigTest (8), render/AxisFormatterTest (10), render/AxisRendererTest (3), render/LineSignalRendererTest (2)
|
||
|
||
**ABI:**
|
||
- Baseline regenerated; apiCheck GREEN
|
||
- `chart-realtime.api`: 161 → 428 LOC
|
||
- `chart-realtime.klib.api`: 211 → 501 LOC
|
||
|
||
**Deviations from plan:**
|
||
- `iosX64` target dropped (Compose-MP 1.11.0 dropped Intel iOS sim variant)
|
||
- `MinMaxLttb` perf 1.80× (paper claims 10× — gap = scalar Kotlin/Native vs Rust+SIMD; ≥1× acceptance met)
|
||
- `signalsArray` cache NOT invalidated by `clear()` (entries preserved per T10 contract)
|
||
|
||
**Highlights vs plan goals:**
|
||
- Interaction layer S0 shipped (was "fatale senza")
|
||
- HARD BREAK on T2/T5 per intake decision (no deprecated bridges)
|
||
- All v0.4.0 deferred items (D1/D3/D4) absorbed
|
||
|
||
---
|
||
|
||
## v1.0.0 — Public Release (PLANNED)
|
||
|
||
Plan: .paul/phases/v1.0.0-public-release-plan.md
|
||
Effort: 2-3 giorni full-time + manual ops, ~10 agent calls
|
||
Prereq: v0.4.0 + v0.5.0 shipped
|
||
|
||
### Highlights
|
||
- Maven Central publishing (`dev.dtrentin:chart-realtime:1.0.0`)
|
||
- GitHub Actions CI (Android + iOS)
|
||
- SwiftUI iOS demo app consuming XCFramework
|
||
- Benchmark vs Vico/KoalaPlot/MPAndroidChart con numeri reali
|
||
- README professionale: GIF + badge + benchmark + onesta comparison table
|
||
- CHANGELOG.md + CONTRIBUTING.md + CODE_OF_CONDUCT.md
|
||
- Performance budget docs (frame time, memory, battery)
|
||
- Logo + banner + demo GIF
|
||
- Tag v1.0.0 + GitHub release con artifact
|
||
|
||
---
|
||
|
||
## Backlog (post v1.0.0)
|
||
- Multi-Y axis / log scale
|
||
- Annotations / threshold lines / regions
|
||
- Headless data export (PNG/CSV)
|
||
- CocoaPods publication
|
||
- Swift Package Manager (SPM)
|
||
- Wasm + desktop (jvm) targets
|
||
- ScatterRenderer / AreaRenderer / BarRenderer (sfrutta SignalRenderer interface)
|
||
- Legend composable
|
||
|
||
---
|
||
*Roadmap updated: 2026-05-21 — post 3-reviewer audit*
|