Orion/3. Resources/Research/prompts/Promp SatsAmp Fluidity.md
Avi a66996ac10 Orion vault — clean initial history
Knowledge vault (Orion/PARA) migrated from the pre-Orion 484vault on
2026-10-01. Deliberately orphaned: prior history contained a plaintext
password and stays local-only on branch archive/pre-boilerplate-history.
Secrets and live Hermes state are gitignored.
2026-10-02 08:34:48 -05:00

3.2 KiB

Here's the full playbook — symptoms, how we cornered each agent, and the exact
commands. Paste the "Recipe" part into any agent for another app.
What was wrong in SatsAmp (3 stacked causes)
1. Main-thread disk I/O inside composition. StationRepository (~290ms) and
StreamRepository (~130ms) read SharedPreferences + parse JSON in their
constructors. Hilt builds singletons lazily, so that cost landed inside the
first page-visit's tap frame.
2. Tab taps animated through every intermediate page, composing/disposing
heavy screens mid-flight, plus the selected-indicator tracked settledPage
(lags the animation).
3. Debug-build tax. First-touch class verification + JIT + shader compile
produced 400-1100ms frames that only exist in debuggable builds. Release (R8 +
install-time AOT) collapsed p99 from 900ms → 18-65ms.
Also fixed along the way: an equalizer composable running
rememberInfiniteTransition in every list row even when paused, one unkeyed
LazyColumn, missing row placement animations.
Recipe: diagnose jank in any Android app
4. Setup. adb devices, note the device. Get tap coordinates once: adb shell
uiautomator dump /sdcard/ui.xml, pull the bounds of the buttons you'll drive.
5. Measure per interaction, not lifetime. Stats accumulate — always reset
right before the action, sleep, then dump:
adb shell dumpsys gfxinfo <pkg> reset > /dev/null
adb shell input tap <x> <y>; sleep 2
adb shell dumpsys gfxinfo <pkg> | grep -E "Total frames|Janky
frames:|95th|99th|Slow UI|Slow issue|input latency"
Key fields: Slow UI thread vs GPU percentiles (in the full dump: 90th gpu
percentile). GPU > 2ms + slow UI thread = CPU-side problem, stop looking at
overdraw/shaders.
6. Idle test — rule out runaway animation. Reset, sleep 6, dump. Total frames
rendered: 0 = rendering is on-demand, nothing spins. Dozens+ frames idle =
find the rememberInfiniteTransition / ticker that's always mounted.
7. Isolate tap path vs content path. Tap the already-selected tab. Clean (0
janky) = the stall is incoming-page work, not ripple/nav handling.
8. StrictMode — catches main-thread I/O with stack traces. Add in
Application.onCreate, gated on debuggable, penaltyLog() only (never crash):
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
    .detectDiskReads().detectDiskWrites().detectNetwork()
    .detectCustomSlowCalls().penaltyLog().build())
Drive the app, then adb shell logcat -d | grep StrictMode. Look for app frames
(at com.yourapp...) and durations — ours showed repository constructors at 130-
290ms firing from hvilViewModel() during composition.
9. Composition timing probe. Wrap page content: remember { nanoTime() } +
SideEffect { log(elapsed) }. Ours showed 6-24ms — proving composition was
innocent and the stall was elsewhere in the frame.
10. Per-frame table for position. adb shell dumpsys gfxinfo <pkg> framestats,
find the Flags= rows missed vsync; compare
PerformTraversalsStart vs IntendedVsync — a big gap before traversals start
- dumpsys gfxinfo without reset shows lifetime stats — always reset per
experiment.
- am profile start/stop produced 0-byte files whenever a tap occurred mid-
trace on this device; in-app probes (StrictMode, FrameMetrics, SideEffect
timers) were more reliable than device-side