Porthole is a debug-only window into a running Android app — for the developer watching it, and the coding agent fixing it.
The runtime starts with your process and finds the Activity itself, so there is nothing to wrap. What it cannot discover is your HTTP and database clients: instrumenting one means being handed the builder before it is built.
plugins {
id("live.gravitylabs.porthole")
}
It puts the runtime on your debug build types and a no-op with the same API on everything else.
import live.gravitylabs.porthole.* OkHttpClient.Builder().installPorthole() Room.databaseBuilder(…).installPorthole()
Ktor and SQLDelight too. Forget one and the lane says so, rather than looking like an app that made no requests.
./gradlew portholeUi
Forwards the port, serves the timeline, opens your browser. No restart, no launch flag.
Every lane shares a single monotonic clock, so a recomposition burst, the state writes that preceded it, and the frames it cost line up in the same column.
A screen recording tells a coding agent nothing. Porthole ships an MCP server that answers against the window you are looking at — recomposition counts, the state keys written just before them, what held the main thread and the stack it was stuck in.
The sample deliberately misbehaves: a ticking value is read inside every list row, and one button blocks the main thread. This is what the window said about it.
one capture from the sample app — your own numbers will differ