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>
181 lines
12 KiB
Markdown
181 lines
12 KiB
Markdown
# 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: v0.5.0-architecture (SHIPPED ✓)
|
||
Phase: All 6 P-groups complete. ABI regenerated. Unify green.
|
||
Plan: .paul/phases/v0.5.0-architecture-plan.md (v2 deltas applied 2026-05-21)
|
||
Status: v0.5.0 closed. Next milestone v1.0.0 public release.
|
||
Last completed: v0.5.0 architecture + interaction (2026-05-21)
|
||
Last activity: 2026-05-21 — v0.5.0 SHIPPED via kmp-manager 6-phase flow. 14 agent calls. 13 plan tasks done (D1, T1-T10, D3, D4). 107 → 207 tests pass on iosSimulatorArm64 (+100). ABI baseline regen: 161/211 → 428/501 LOC. HARD BREAK on LodMode/LodDecimator/targetFps. Interaction layer (pinch/pan/crosshair) shipped. MinMaxLTTB SOTA 1.80× faster than pure LTTB. All toolchain bumps (Kotlin 2.3.21, Compose-MP 1.11.0, AGP 9.2.0, Gradle 9.5.1). kotlinx-datetime dropped (kotlin.time.Clock). iosX64 dropped (Compose-MP 1.11.0 incompat).
|
||
|
||
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
|
||
✓ ✓ ✓ [v0.5.0 architecture + interaction SHIPPED — v0.5.0-SNAPSHOT]
|
||
```
|
||
|
||
## 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<Map>; 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*
|