Three drawing-path defects on the pen clients:
- The Apple in-range heartbeat was re-armed on every emit (~120 run-loop
mutations/s during a stroke) and lived in .default run-loop mode only — a
tracking-mode excursion >200 ms with a stationary pen crossed the host's
force-release failsafe and dropped the held stroke. Now one long-lived
50 ms timer in .common mode, resending only after ≥50 ms of send silence.
- Both clients TRUNCATED an over-8-sample coalesced run instead of splitting
it into consecutive batches (the send_pen contract): Apple suffix(8)
dropped the oldest samples, the Android JNI clamped count. An over-cap run
means the UI thread hitched — exactly when dropping stroke geometry hurts
most. Apple splits in emit(); the Android JNI loops send_pen over ≤8-sample
chunks (Kotlin's per-emit ceiling is now 64 = a >250 ms stall).
- The iOS letterbox mapper did a lock+FFI currentMode() read per coalesced
sample on the main thread, contending the ABI lock the batch send takes
next — now a 250 ms TTL cache (mid-stream requestMode still tracked).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>