# Project State ## Project Reference See: .paul/PROJECT.md (updated 2026-05-21) **Core value:** Android devs plot 200Hz+ multi-signal sensor data without frame drops **Current focus:** v0.4.0 portfolio hardening SHIPPED — next v0.5.0 architecture + interaction ## Current Position Milestone: v1.0.0-public-release (IN PROGRESS — autonomous tasks done, user-blocked tasks pending) Phase: Portfolio prep autonomous batch shipped (debt partial, docs, CI, demos). User-side tasks pending. Plan: .paul/phases/v1.0.0-public-release-plan.md Status: 5/10 plan tasks done (T2, T3, T5, T6, T8 partial + T9 perf docs + partial debt). 5 blocked on user (T1 Maven Central, T4 benchmark vs competitors, T7 assets, T10 tag/release, app rename). Last completed: v1.0.0 portfolio prep autonomous batch (2026-05-22) Last activity: 2026-05-22 — Autonomous portfolio prep batch. 1 debt warning cleared (klib non-packed compat removed; 8 remain blocked behind com.android.kotlin.multiplatform.library migration). Docs: CHANGELOG.md, CONTRIBUTING.md, chart-realtime/README.md rewrite, docs/PERFORMANCE.md. CI: .github/workflows/{ci,release}.yml + dependabot + PR template + 2 issue templates. Android demo (T8) polished: Material3 control panel (sample rate slider, signal count, waveform per signal, window seconds, LoD picker, axis label mode, formatter picker, interaction toggle, theme switch, status row). iOS SwiftUI demo (T3 scaffold) via SPM Package.swift + sources; xcodebuild builds green; smoke test confirms XCFramework consumable from Swift; ComposeUIViewController shim deferred to v1.0.0 T1. Progress: - v0.1.0 milestone: [██████████] 100% SHIPPED - v0.2.0 milestone: [██████████] 100% SHIPPED - v0.3.0 milestone: [██████████] 100% SHIPPED - v0.4.0 milestone: [██████████] 100% SHIPPED - v0.5.0 milestone: [██████████] 100% SHIPPED - v1.0.0 milestone: [█████░░░░░] 50% portfolio prep done, user-blocked items pending Progress: - v0.1.0 milestone: [██████████] 100% SHIPPED - v0.2.0 milestone: [██████████] 100% SHIPPED - v0.3.0 milestone: [██████████] 100% SHIPPED - v0.4.0 milestone: [██████████] 100% SHIPPED - v0.5.0 milestone: [██████████] 100% SHIPPED ## Loop Position ``` PLAN ──▶ APPLY ──▶ UNIFY ✓ ◐ ✓ [v1.0.0 partial — autonomous batch done, user-blocked items pending] ``` ## v1.0.0 deferred (need user) - **T1 Maven Central publish** — Sonatype OSSRH account + GPG key + DNS TXT proof for `dev.dtrentin` namespace - **T4 Benchmark vs competitors** — methodology decision (JMH? androidx.benchmark? both?), Maven coords for Vico/KoalaPlot/MPAndroidChart, hardware target (Pixel 6 / iPhone 13 vs CI runners) - **T7 Logo / banner / GIF** — design assets (manager cannot generate raster/vector art) - **T10 Tag + release + push to GitHub** — user wants to wait on GitHub (potential project rename). Currently remote = ssh://3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git - **App rename** — user mentioned considering rename; deferred - **ComposeUIViewController shim** (T1 dependency) — small Kotlin shim in `chart-realtime/src/iosMain/` exporting `RealtimeChartViewController(state, xWindowSeconds): UIViewController` to enable real SwiftUI demo ## v1.0.0 deferred (out-of-scope debt) - **Remaining 8 deprecation warnings** (KGP 2.3 / AGP 9 `:app` plugin + `libraryVariants` API + AGP-newDsl) blocked behind migration to `com.android.kotlin.multiplatform.library` 9.2.0. Migration forces ABI baseline regen + restructured Maven publication graph + XCFramework hierarchy expansion. Deferred as dedicated post-1.0.0 ticket. ## Accumulated Context ### Decisions | Decision | Phase | Impact | |----------|-------|--------| | Canvas custom rendering (no lib) | Init | Core architecture | | Render/data decoupled withFrameNanos | Init | RenderLoop design | | TieredBuffer over flat ring | v0.2.0 | O(N×constant) memory, 20+ signals | | MIN_MAX tier binning (not MEAN) | v0.2.0 | Composable, no artifacts at tier boundaries | | MEAN removed from LodMode | v0.2.0 | Cleaner API, no synthetic values | | LodDecimator as class (pre-alloc scratch) | v0.2.0 | Zero GC at render time | | Sensor cap 200Hz (5_000μs) | v0.2.0 | Thermal/battery optimization | | Package dev.dtrentin | v0.2.0 | Personal project, not company | | iOS targets shipped | v0.3.0 | iosX64/iosArm64/iosSimulatorArm64 + Platform.ios.kt (iosX64 dropped in v0.5.0 D1) | | Drop iosX64 target | v0.5.0 D1 | Compose-MP 1.11.0 no ios_x64 variant; Apple deprecated Intel Mac sims Xcode 15+; iosArm64 + iosSimulatorArm64 cover modern devs | | iOS build pipeline green | v0.3.0 iOS | 33 tests pass on iOS sim, XCFramework "ChartRealtime" produced | | timeIntervalSinceReferenceDate + epoch offset | v0.3.0 iOS | K/Native binding doesn't expose timeIntervalSince1970; use 978_307_200s constant | | applyDefaultHierarchyTemplate() explicit | v0.3.0 iOS | iosMain/iosTest auto-wired across 3 iOS targets | | Manual format helpers in AxisRenderer | v0.3.0 iOS | String.format JVM-only; private formatFixed/formatScientific/pow10 | | kotlin.concurrent.Volatile + kotlin.jvm.JvmName imports | v0.3.0 iOS | Multiplatform replacements for JVM-default annotations | | MIT License | v0.4.0 T1 | Personal project, permissive, no patent grant required | | explicitApi() Strict + BCV 0.16.3 + klib enabled | v0.4.0 T2 | ABI lockdown active; chart-realtime.api 161 LOC + .klib.api 211 LOC baseline | | kotlinx-datetime 0.6.2 (NOT 0.7.x) | v0.4.0 T4 | Kotlin 2.1.0 compat; bump to 0.7.x when Kotlin → 2.1.20+ in v0.5.0 | | CircularBuffer.writeIndex Long | v0.4.0 T5 | Overflow 124d @ 200Hz fixed | | hasData boolean flag | v0.4.0 T8 | Replace Float.MAX_VALUE/POSITIVE_INFINITY sentinels in TieredBuffer + RealtimeChart | | _signals as @Volatile copy-on-write Map | v0.4.0 T9 | Replace MutableState; eliminate snapshot writes from data thread for collection | | dataVersion as mutableLongStateOf | v0.4.0 T10 | Compose-observable; drives recomposition data-driven; idle = 0 recompositions | | applySnapshot retry helper (Snapshot.withMutableSnapshot + SnapshotApplyConflictException) | v0.4.0 T9 | Thread-safe push from background thread | | RenderLoop.kt deleted | v0.4.0 T6/T10 | rememberFrameTick removed; targetFps deprecated (KDoc + @Deprecated) | | Per-signal scratch arrays in SignalEntry | v0.4.0 T11 | Single buffer snapshot per signal per frame (was 2); ~1.14 MB/signal | | M4 binning (firstTs/V, minTs/V, maxTs/V, lastTs/V) | v0.4.0 T12 | Replace double-push min/max sentinel; TIER1/2 CAPACITY *4; no vertical spikes at bin boundaries | | NaN/Inf/backward-timestamp guards on push | v0.4.0 T13 | Silent drop; KDoc contract added; per-signal lastPushedTs tracking | | NumberFormat internal object | v0.4.0 T14 | Extracted from AxisRenderer for testability; 38 tests pinned behavior | ### Deferred Issues | Issue | Origin | Effort | Revisit | |-------|--------|--------|---------| | ~~Dirty flag render (skip frame if no new data)~~ | v0.2.0 | S | ✓ v0.3.0 | | ~~targetFps=30 default~~ | v0.2.0 | S | ✓ v0.3.0 | | ~~iOS expect/actual beyond currentTimeMs~~ | Init | L | ✓ v0.3.0 | | ~~KDoc on public API surface~~ | v0.2.0 | M | ✓ v0.3.0 | | ~~README with integration example~~ | v0.2.0 | S | ✓ v0.3.0 | | ~~iOS build/test pipeline (compile + iosTest + XCFramework)~~ | v0.3.0 plan | M | ✓ v0.3.0 iOS | | Publish to Maven Central / GitHub Packages | v0.2.0 | M | v1.0.0 | | iOS demo app (SwiftUI) | v0.3.0 iOS | L | v1.0.0 | | CI workflow for iOS build (GitHub Actions) | v0.3.0 iOS | M | v1.0.0 | | Compose compiler unstable warning on SignalConfig | v0.3.0 iOS | S | v0.5.0 | | ~~LICENSE missing~~ | v0.4.0 plan | S | ✓ v0.4.0 T1 (MIT) | | ~~explicitApi + BCV~~ | v0.4.0 plan | M | ✓ v0.4.0 T2 | | ~~Internal lockdown 6 leaked symbols~~ | v0.4.0 plan | M | ✓ v0.4.0 T3 | | ~~Drop expect/actual currentTimeMs~~ | v0.4.0 plan | S | ✓ v0.4.0 T4 | | ~~CircularBuffer.writeIndex Long~~ | v0.4.0 plan | S | ✓ v0.4.0 T5 | | ~~@Suppress + !! cleanup~~ | v0.4.0 plan | S | ✓ v0.4.0 T6 | | ~~Dead config removal~~ | v0.4.0 plan | S | ✓ v0.4.0 T7 | | ~~POSITIVE_INFINITY sentinel replace~~ | v0.4.0 plan | S | ✓ v0.4.0 T8 | | ~~Compose snapshot thread safety~~ | v0.4.0 plan | M | ✓ v0.4.0 T9 | | ~~dataVersion mutableLongStateOf~~ | v0.4.0 plan | M | ✓ v0.4.0 T10 | | ~~Single snapshot per signal per frame~~ | v0.4.0 plan | M | ✓ v0.4.0 T11 | | ~~M4 binning Tier1/Tier2~~ | v0.4.0 plan | M | ✓ v0.4.0 T12 | | ~~NaN/Inf/backward-timestamp guards~~ | v0.4.0 plan | S | ✓ v0.4.0 T13 | | ~~AxisRenderer format helpers test~~ | v0.4.0 plan | M | ✓ v0.4.0 T14 (38 tests) | | ~~TieredBuffer + LTTB test gap~~ | v0.4.0 plan | M | ✓ v0.4.0 T15 (25 tests) | | Bump kotlinx-datetime 0.6.2 → 0.7.x | v0.4.0 T4 | S | v0.5.0 (needs Kotlin 2.1.20+) | | Long overflow at |value| ≥ 1e19 in NumberFormat.formatFixed | v0.4.0 T14 | S | v0.5.0 (currently safe via routing) | | LTTB upper bound inclusive vs snapshot half-open semantic mismatch | v0.4.0 T15 | S | v0.5.0 | | Remove ChartConfig.targetFps (now @Deprecated, ignored) | v0.4.0 T10 | S | v0.5.0 | | Memory budget: ~1.14 MB/signal post-CAPACITY *4 (was ~760 KB pre-bump) | v0.4.0 T12 | doc | document in README post-v0.4.0 | ### Reviewer Audit Findings (2026-05-21) Source: 3 parallel reviewer agents (android-reviewer + mobile-performance-reviewer + mobile-architect). **S0 (block portfolio) findings:** - LICENSE missing → legalmente nessuno può usare la lib - Compose snapshot violation: `push()` mutate MutableState da thread arbitrario senza withMutableSnapshot - `dataVersion @Volatile` NON è Compose-observable, recomposition driven solo da frameTick side-effect - Render NON zero-alloc: Pair + Stroke + TextLayoutResult + Strings per tick per frame (~50-100 KB/s) - Snapshot doppia per signal per frame (8x lavoro su 4 segnali) - No interaction layer (touch/zoom/pan) → fatale per "real-time chart" piece **S1 (visibili) findings:** - CircularBuffer.writeIndex Int overflow dopo 124 giorni @ 200Hz - Tier1/Tier2 binning C7 artifacts (push doppio min poi max stesso ts) - 6 leaked internal symbols pubblici (CircularBuffer, TieredBuffer, LodDecimator, dataVersion, rememberFrameTick, capacity constants) - 3 dead config field (maxBufferSeconds, xLabelDecimals, SignalConfig.label) - `@Suppress("UNUSED_EXPRESSION")` hack + `!!` non-null assertion in render hot path - Test gap: AxisRenderer format helpers untested + buggy (Q4 formatFixed Long overflow), TieredBuffer untested (143 LOC), LTTB branch untested - `expect/actual currentTimeMs` over-engineered — kotlinx-datetime 0.7.0+ replaces it - iOS targets build ma README + samples Android-only **Claims aggiustati:** - "Zero GC at render time" → **falso oggi**, vero post v0.5.0 T8/T9 - "Lock-free SPSC ring buffer" → impreciso, fragile JMM su Native - "200Hz sustained" → plausibile JVM, borderline mid Android multi-signal pre-v0.5.0 T11 - "1h buffer" → vero ma costa 720KB+ per signal (non documentato) **Posizionamento mercato:** Nicchia genuinamente vuota: nessun KMP chart lib (Vico, KoalaPlot, AAY-chart, netguru) targeta real-time 200Hz multi-signal con bounded memory. Differentiation reale ma serve interaction layer per essere prendibile sul serio. ### Blockers/Concerns - ~~LICENSE missing~~ → ✓ MIT shipped in v0.4.0 T1 - ~~API surface non lockdown~~ → ✓ explicitApi + BCV shipped in v0.4.0 T2-T3 - ~~Compose snapshot thread safety~~ → ✓ withMutableSnapshot + retry shipped in v0.4.0 T9 - No interaction layer (touch/zoom/pan) → blocking "real-time chart" claim (v0.5.0 scope) - targetFps deprecated but not removed → minor noise in public API (v0.5.0) ## Session Continuity Last session: 2026-05-21 (v0.4.0 portfolio hardening shipped via kmp-manager group-by-group flow) Stopped at: v0.4.0 closed. 15 plan tasks done. 107 tests pass. Full verify suite green. Next action: kickoff v0.5.0 architecture + interaction layer Resume context: - Library: chart-realtime, dev.dtrentin:chart-realtime:0.5.0-SNAPSHOT (version unbumped post-v0.4.0; bump deferred to v1.0.0 release) - App: AndroidChartsApp consuming library, builds clean - Remote: ssh://git@3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git - Package: dev.dtrentin.chart - iOS: 3 targets compile, link, test (107 tests). XCFramework name `ChartRealtime`, baseName CamelCase. - Local Mac setup: Xcode.app at /Applications/Xcode.app, iOS sim runtime installed (iPhone 17 family), local.properties points to ~/Library/Android/sdk, JDK 21 Temurin via JAVA_HOME. - ABI baseline committed: chart-realtime/api/chart-realtime.api (161 LOC) + chart-realtime.klib.api (211 LOC) - Plans: v0.4.0 ✓ SHIPPED · v0.5.0 + v1.0.0 PLANNED - Effort residuo verso v1.0.0: ~7-10 giorni full-time ### v0.4.0 close summary (2026-05-21) 15 plan tasks executed across 8 P-groups via kmp-manager: - P0: T1 (LICENSE MIT) + T7 (dead config removal) - P1: T2 (explicitApi + BCV 0.16.3 + klib enabled) - P2: T3 (internal lockdown) + T4 (kotlinx-datetime 0.6.2) - P3: T5 (writeIndex Long) + T8 (hasData boolean flag) - P4: T6 (RealtimeChart cleanup) + T9 (snapshot thread safety) + T10 (mutableLongStateOf) - P5: T11 (single snapshot per signal) + T12 (M4 binning) - P6: T13 (NaN/Inf/backward guards) - P7: T14 (NumberFormat extract + 38 tests) + T15 (TieredBuffer/LTTB +25 tests) Test count: 33 → 107 (+74). ABI: 211/272 → 161/211 LOC (locked, baseline committed). RenderLoop.kt deleted. Platform.kt + .android.kt + .ios.kt deleted. 0 `!!` assertions in commonMain. 0 `@Suppress` hacks. explicitApi Strict. Deviations from plan: - T4 kotlinx-datetime 0.6.2 instead of 0.7.x (Kotlin 2.1.0 compat — 0.7.x needs Kotlin 2.1.20+). Bump deferred to v0.5.0. - T7 ChartConfig.targetFps NOT removed (would break ABI of data class). Marked @Deprecated, ignored at runtime. Full removal in v0.5.0. - T11 memory per signal ~1.14 MB (plan estimate was ~760 KB based on pre-bump TOTAL_CAPACITY). Acceptable. - T13 unknown-signal push no longer bumps dataVersion (was bumping in v0.3.0). Existing test `push_unknownSignal_stillIncrementsDataVersion` replaced with `pushIgnoresUnknownSignalName` per new contract. --- *STATE.md — Updated after every significant action*