Performance / engineering note
A window, not a copy
How the inspector reads gigabytes of on-device storage without freezing your app — or our dashboard.12 min read · NativeScope engineeringThe problem
The trap every inspector falls into
The obvious design serializes every key, row and value, then ships the copy to a desktop dashboard. It looks fine against twenty keys. Against a real database, the app stalls while stringifying and the browser chokes while parsing a payload it never needed.
A faster serializer, compression or a heavier grid cannot fix that shape of problem. The cost is still proportional to the size of the data. You cannot optimize your way out of moving a gigabyte you never needed to move.
The thesis
One rule: the window, not the copy
The values stay where they live. The interface slides a window over them, and the rendered row count remains tied to the viewport. Key inventory is the explicit exception: AsyncStorage and MMKV expose all names through getAllKeys(), so NativeScope cannot make that provider operation constant-time. It keeps the expensive value reads, transfer and DOM work bounded after enumeration.
The contract
The budgets we refuse to break
A principle you cannot measure is just a slogan. The hard byte, preview and page limits are covered by CI tests. Scheduling and rendering behavior are exercised by the scale runtime and browser QA rather than presented as synthetic millisecond guarantees.
| Budget | Limit | Why it exists |
|---|---|---|
| Single WebSocket message | ≤ 256 KB | Measured in UTF-8 bytes and covered by budget tests. |
| Key-value preview | ≤ 64 KB | A complete value moves to the cancelable stream lane. |
| SQLite cell preview | ≤ 4 KB | The grid never receives a full oversized cell by accident. |
| Value reads per key page | ≤ 500 | Names may be enumerated by the provider; values stay paged. |
| Dashboard DOM | O(viewport) | Virtualization bounds mounted rows as loaded pages grow. |
| Runtime scheduling | cooperative | Value batches yield to the app event loop between slices. |
The architecture
The path a byte takes
Three actors sit between a byte on the device and a pixel on screen. The runtime reads one requested page and truncates values to bounded previews. A local bridge relays bounded frames. The Studio virtualizes the viewport and avoids rebuilding off-screen SQLite rows.
On the device
Read little, yield often
Key names are fetched first. One page is read in small batches and each value becomes a preview before crossing the wire. Between batches, NativeScope returns the JavaScript thread to the event loop, keeping taps, animations and network callbacks alive.
Pagination uses keyset cursors instead of OFFSET, so page one and page one hundred thousand have the same shape of cost. Search runs next to the data and returns only matches, instead of transferring a store to filter it elsewhere.
The transport
Stream, never dump
Large values use a separate lane: bounded chunks with a beginning, an end and a running checksum. Streams are cancelable, and Chromium exports can flow directly to disk. Browsers without the File System Access API use a clearly signaled buffered fallback.
The dashboard
A viewport over millions
A million-key list still mounts roughly thirty recycled DOM nodes. Large JSON collections use the same virtual-row discipline, while node counting stops at a fixed diagnostic cap. Live SQLite refreshes re-query the visible window, not everything the user has ever scrolled through. Fluidity on both ends is one rule applied twice.
In the tables
Two grids, one window
The same discipline reaches the two places where NativeScope shows data in bulk. A million-row SQLite table and a JSON array with tens of thousands of items both render as a small set of recycled rows. Global key search and SQLite search run next to the data, so matches return without transferring the underlying values or rows first. Once a JSON value is explicitly loaded, its visual filter runs locally over that value.
Live refresh follows the same rule. When data changes, NativeScope re-queries only the visible rows by keyset cursor. The cost stays proportional to the viewport, not to everything the user has already scrolled through.
The guarantee
100% of the data, always
A preview is a display choice, never a limit on access. It carries the true size and an explicit truncation marker. The complete value is always available on demand or as a streamed file, verified against accidental corruption in transit.
We stopped copying data to show it and started opening a window onto data that stays where it is. The budgets, cursors, streams and virtualization are what keep that window honest at the scale of gigabytes.

