# v0.5.0 — Architecture + Interaction Plan ## Source mobile-architect verdict 2026-05-21: "Library is a closed renderer with single skin. Anything beyond first-party use case requires forking." Senza interaction layer "no senior reviewer will take a real-time chart claim seriously." ## Goal Aprire estensibilità + aggiungere interaction layer (S0 per portfolio). MinMaxLTTB SOTA algorithm. Config split. Bisect tier snapshot per perf. ## Prereq v0.4.0 shipped (hygiene + correctness). ## Scope IN - Interaction layer: pinch zoom, drag pan, crosshair, tap-to-inspect - Renderer/LodStrategy/AxisFormatter interfaces - MinMaxLTTB algorithm (replaces both MIN_MAX and LTTB attuali) - Bisect-based windowed snapshot (30x perf win) - ChartConfig split DataConfig/AxisConfig/RenderConfig - Compose stability annotations (@Stable, @Immutable) ## Scope OUT - Multi-Y axis / log scale → v0.6.0+ - Annotations / threshold lines → v0.6.0+ - Headless data export → backlog --- ## Tasks ### T1 — Bisect tier snapshot (perf S0) - Files: `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/buffer/CircularBuffer.kt`, `TieredBuffer.kt` - Goal: `snapshotWindow(startMs, endMs, outTs, outV): Int` bisect su timestamps monotonici. Cuts 30x copies su 10s window in 5min buffer. - Agent: data-impl + mobile-performance-reviewer review - Verify: macrobench frame time -50% addizionale post v0.4.0 T11 ### T2 — LodStrategy interface + MinMaxLTTB impl - New files: - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/lod/LodStrategy.kt` - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/lod/MinMaxLttb.kt` - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/lod/MinMaxLod.kt` - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/lod/LttbLod.kt` - Move + rename: `buffer/LodDecimator.kt` → `lod/LodDecimator.kt` (delegate to strategy) - Goal: interface `LodStrategy { fun decimate(...): Int }`. Default = `MinMaxLttb` (10x faster than LTTB puro per arXiv 2305.00332). - API change: `LodMode` enum → `LodStrategy` interface. `ChartConfig.lodMode: LodMode` → `lodStrategy: LodStrategy`. Backward compat: `LodMode.MIN_MAX_LTTB` shorthand. - Agent: kmp-architect + data-impl - Verify: nuovi test `MinMaxLttbVisualFidelityTest`, perf ≥ MIN_MAX puro su large datasets - Pattern: [MinMaxLTTB arXiv 2305.00332](https://arxiv.org/pdf/2305.00332), [Rust ref impl](https://github.com/andrei-ng/minmaxlttb-rs) ### T3 — SignalRenderer interface - New file: `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/SignalRenderer.kt` (interface) - Move impl → `LineSignalRenderer.kt` - Goal: pluggable rendering. Future-proof per `ScatterRenderer`, `AreaRenderer`, `BarRenderer`. Default `LineSignalRenderer`. - `SignalConfig.renderer: SignalRenderer = LineSignalRenderer` (per-signal opt-in) - Agent: kmp-architect - Verify: existing rendering unchanged, plus `customRendererPlugsIn` test ### T4 — AxisFormatter interface - New file: `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/AxisFormatter.kt` - Default impls: `TimeAxisFormatter` (current behavior), `DecimalAxisFormatter`, `DateTimeAxisFormatter`, `UnitAxisFormatter("mV", "°C", "Hz")`. - `AxisConfig.xFormatter: AxisFormatter = TimeAxisFormatter`, `yFormatter: AxisFormatter = DecimalAxisFormatter` - Agent: kmp-architect + ui-impl - Verify: user can pass `UnitAxisFormatter("ms")` and labels show "5 ms" ### T5 — Split ChartConfig → DataConfig + AxisConfig + RenderConfig - File: `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/model/ChartConfig.kt` - New: `DataConfig`, `AxisConfig`, `RenderConfig`. `ChartConfig` = wrapper o builder DSL. - `targetFps: Int?` → `sealed class FrameRate { object Display; data class Fixed(val fps: Int) }` - Mark old `ChartConfig` ctor `@Deprecated(level = WARNING)` con `ReplaceWith`. - Agent: kmp-architect - Verify: README example updated, compile green con deprecation warning ### T6 — Compose stability annotations - Files (tutti i data class in model/): - `ChartConfig.kt`, `SignalConfig.kt`, `ChartTheme.kt`, `T0.kt`, `YRange.kt`, `LodMode.kt`, `AxisLabelMode.kt` - `RealtimeChartState.kt` - Action: `@Immutable` su data class + sealed root. `@Stable` su `RealtimeChartState`. - Agent: compose-guide - Verify: Compose compiler report `chart-realtime` mostra 0 unstable classes nel public API. `./gradlew :chart-realtime:compileReleaseKotlinAndroid -Pkotlinx.compose.report` etc. ### T7 — Interaction layer S0 (touch/zoom/pan/crosshair) - New files: - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/interaction/ChartInteractionState.kt` - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/interaction/InteractionConfig.kt` - Modify: `RealtimeChart.kt` aggiunge `Modifier.pointerInput` con `detectTransformGestures` (pinch) + `detectDragGestures` (pan) + `detectTapGestures` (crosshair toggle). - State: - `viewportOffsetMs: Long` — pan offset history - `viewportScale: Float` — zoom factor on xWindowSeconds - `mode: ViewportMode { Following, Frozen, History(offsetMs) }` - `crosshair: CrosshairState?` — pixel position + projected (timestampMs, value) per ogni signal - Inverse projection helper (pixel → data) per tooltip - Agent: ui-impl + compose-guide - Verify: - pinch zoom restringe/allarga xWindowSeconds - drag pan sposta viewport (mode Frozen automatically) - tap mostra crosshair con valori istantanei - tap su crosshair attivo → dismiss - swipe a destra estrema → mode Following (auto-scroll resumes) - New tests: `ChartInteractionStateTest`, integration test composable ### T8 — Cache Stroke + TextStyle + TextLayoutResult per zero-alloc reale - Files: - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/SignalRenderer.kt:50` — `Stroke` cached per signal - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/AxisRenderer.kt:54-131` — `TextStyle` hoisted in `remember`, label `TextLayoutResult` cached via `remember(label, style)` - Action: introduce `internal class FrameCaches` holding stroke per signal + label LRU. - Agent: compose-guide + mobile-performance-reviewer - Verify: allocation tracker show < 1 KB/s steady-state at 30fps, 4 signals - Pattern: [TextMeasurer LRU - composables.com](https://composables.com/jetpack-compose/androidx.compose.ui/ui-text/classes/TextMeasurer/api), [Modifier.drawWithCache - Android Devs](https://developer.android.com/develop/ui/compose/graphics/draw/overview) ### T9 — Replace Pair return + Map iteration alloc - Files: - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/AxisRenderer.kt:144-152` — `resolveYRange` riempie `FloatArray(2)` out param - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/RealtimeChart.kt:70,79,93` — Map iter via cached `signalsArray` - `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/RealtimeChartState.kt` — invalidate signalsArray su addSignal/removeSignal - Agent: mobile-performance-reviewer + data-impl - Verify: Map iter allocation count = 0 in macrobench ### T10 — clear() / reset() su RealtimeChartState - File: `chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/RealtimeChartState.kt` - Goal: `fun clear()` purge tutti i buffer + reset dataVersion + reset resolvedT0Ms. Public API. - Agent: data-impl - Verify: test `clearResetsState` --- ## Acceptance v0.5.0 - A1: `LodStrategy` interface + `MinMaxLttb` default, perf ≥ MIN_MAX puro su 100k+ samples - A2: `SignalRenderer` interface, default `LineSignalRenderer`, user custom renderer impl-able in 50 LoC - A3: `AxisFormatter` interface con 4 default impls (Time, Decimal, DateTime, Unit) - A4: ChartConfig split, FrameRate sealed, deprecated old API con ReplaceWith - A5: Compose compiler report: 0 unstable public types - A6: Interaction layer: pinch zoom + drag pan + crosshair tap funzionanti - A7: Bisect snapshot taglia copy 30x su 10s window in 5min buffer (macrobench) - A8: Render zero-alloc reale (< 1 KB/s in macrobench steady) - A9: `RealtimeChartState.clear()` API + test ## Parallelism | Group | Tasks | |-------|-------| | P0 | T1, T6, T10 (parallel) | | P1 | T2, T3, T4 (interfaces parallel) | | P2 | T5 (config split, depends on T2-T4 interfaces) | | P3 | T7 (interaction layer, biggest task) | | P4 | T8, T9 (perf finishing) | ## Estimated agent calls ~12 agent calls. ~5-7 giorni full-time. ## Risks - T2 (MinMaxLTTB): break LodMode enum API → migration required by consumers - T7 (interaction): biggest scope, multi-modal touch testing manual - T5 (config split): breaking API change, mitigare con typealias + deprecated factory ## Verification commands ```bash ./gradlew :chart-realtime:apiCheck ./gradlew :chart-realtime:iosSimulatorArm64Test ./gradlew :app:installDebug # manual interaction test ./gradlew :chart-realtime:assembleRelease ```