KMPCharts/.paul/ROADMAP.md
Trentin Davide af6814e2b8 feat(chart): v0.5.0 architecture + interaction
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>
2026-05-22 00:05:43 +02:00

224 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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*