Ship 15-task plan via kmp-manager group-by-group flow. Library moves
from "prototype" to "senior-engineer-built KMP library" per 3-reviewer
audit (android-reviewer + mobile-performance-reviewer + mobile-architect).
Critical fixes:
- LICENSE MIT (T1) — legal blocker resolved
- explicitApi() Strict + BCV 0.16.3 + klib enabled (T2) — ABI locked
baseline: chart-realtime.api 161 LOC + chart-realtime.klib.api 211 LOC
- Internal lockdown: CircularBuffer, TieredBuffer, LodDecimator,
dataVersion, rememberFrameTick (T3)
- kotlinx-datetime 0.6.2 replaces expect/actual currentTimeMs (T4)
Platform.kt + .android.kt + .ios.kt deleted
- CircularBuffer.writeIndex Int -> Long (T5) — fix 124d overflow @ 200Hz
- Remove !! and @Suppress("UNUSED_EXPRESSION") from commonMain (T6)
- Remove dead config: maxBufferSeconds, xLabelDecimals,
SignalConfig.label (T7)
Correctness:
- POSITIVE_INFINITY/Float.MAX_VALUE sentinels -> hasData flag (T8)
- Snapshot.withMutableSnapshot + SnapshotApplyConflictException retry
in push/addSignal/removeSignal (T9)
- dataVersion mutableLongStateOf — Compose-observable, idle = 0
recompositions (T10) — RenderLoop.kt deleted, targetFps @Deprecated
- Single buffer.snapshot() per signal per frame via SignalEntry
per-signal scratch arrays (T11) — render-time snapshot cost halved
- M4 binning Tier1/Tier2: firstTs/V + minTs/V + maxTs/V + lastTs/V
with chronological dedup (T12) — no C7 vertical spike artifacts.
TIER1/TIER2 CAPACITY x2 -> x4 (TOTAL 77.4k -> 94.8k)
- NaN/Inf/backward-timestamp guards on push (T13) — silent drop,
per-signal lastPushedTs tracking, KDoc contract
Test gap fill:
- NumberFormat extracted from AxisRenderer to internal object (T14)
+38 tests pinning rounding/scientific/time-format behavior
- TieredBufferTest +15 tests: tier roll, window-crossing, clear,
edge cases (T15)
- LodDecimatorTest +10 LTTB-specific branch coverage tests (T15)
Test count: 33 -> 107 (+74).
ABI: 211/272 -> 161/211 LOC (locked, baseline committed).
Deferred to v0.5.0:
- Bump kotlinx-datetime 0.6.2 -> 0.7.x (needs Kotlin 2.1.20+)
- Remove ChartConfig.targetFps (currently @Deprecated, ignored)
- NumberFormat Long overflow @ 1e19 (safe via current routing)
- LTTB upper-bound inclusive vs snapshot half-open semantic mismatch
- Macrobench setup for -30% frame-time verification
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.7 KiB
8.7 KiB
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): Intbisect 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.ktchart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/lod/MinMaxLttb.ktchart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/lod/MinMaxLod.ktchart-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:
LodModeenum →LodStrategyinterface.ChartConfig.lodMode: LodMode→lodStrategy: LodStrategy. Backward compat:LodMode.MIN_MAX_LTTBshorthand. - Agent: kmp-architect + data-impl
- Verify: nuovi test
MinMaxLttbVisualFidelityTest, perf ≥ MIN_MAX puro su large datasets - Pattern: MinMaxLTTB arXiv 2305.00332, Rust ref impl
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. DefaultLineSignalRenderer. SignalConfig.renderer: SignalRenderer = LineSignalRenderer(per-signal opt-in)- Agent: kmp-architect
- Verify: existing rendering unchanged, plus
customRendererPlugsIntest
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
ChartConfigctor@Deprecated(level = WARNING)conReplaceWith. - 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.ktRealtimeChartState.kt
- Action:
@Immutablesu data class + sealed root.@StablesuRealtimeChartState. - Agent: compose-guide
- Verify: Compose compiler report
chart-realtimemostra 0 unstable classes nel public API../gradlew :chart-realtime:compileReleaseKotlinAndroid -Pkotlinx.compose.reportetc.
T7 — Interaction layer S0 (touch/zoom/pan/crosshair)
- New files:
chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/interaction/ChartInteractionState.ktchart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/interaction/InteractionConfig.kt
- Modify:
RealtimeChart.ktaggiungeModifier.pointerInputcondetectTransformGestures(pinch) +detectDragGestures(pan) +detectTapGestures(crosshair toggle). - State:
viewportOffsetMs: Long— pan offset historyviewportScale: Float— zoom factor on xWindowSecondsmode: 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—Strokecached per signalchart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/AxisRenderer.kt:54-131—TextStylehoisted inremember, labelTextLayoutResultcached viaremember(label, style)
- Action: introduce
internal class FrameCachesholding 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, Modifier.drawWithCache - Android Devs
T9 — Replace Pair<Float,Float> return + Map iteration alloc
- Files:
chart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/render/AxisRenderer.kt:144-152—resolveYRangeriempieFloatArray(2)out paramchart-realtime/src/commonMain/kotlin/dev/dtrentin/chart/RealtimeChart.kt:70,79,93— Map iter via cachedsignalsArraychart-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:
LodStrategyinterface +MinMaxLttbdefault, perf ≥ MIN_MAX puro su 100k+ samples - A2:
SignalRendererinterface, defaultLineSignalRenderer, user custom renderer impl-able in 50 LoC - A3:
AxisFormatterinterface 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
./gradlew :chart-realtime:apiCheck
./gradlew :chart-realtime:iosSimulatorArm64Test
./gradlew :app:installDebug # manual interaction test
./gradlew :chart-realtime:assembleRelease