September 2, 2026
Rebuilding the session page into an actual workbench
We turned the customer session page from a phone stuck in a 300px box into a stage-and-dock workbench across three staged releases, and found a real tap-scaling bug along the way.
The page you land on when you rent a phone used to look like a marketing page that accidentally had a device on it: a vertical stack of cards, and your actual rented phone living inside a 300-pixel box near the top, like a thumbnail of itself. We rebuilt it into something that behaves like a workbench — a phone on a stage, and a dock of tools around it — across three staged releases, each proven on a real Pixel 7a before the next one started.
Why the old page felt slow
Before touching layout, we chased down why the mirror felt laggy under your cursor, because no amount of redesign fixes a page that lags. Two things were happening. First, every single pointer move re-rendered the component that tracked cursor position — hundreds of renders a second on an active drag. Second, each move waited on its own WebSocket round trip to finish before the next one could be sent, so input was serialized behind network latency instead of firing ahead of it. Keyboard input, the scroll wheel, and clipboard paste weren't wired up at all, even though the mirror library underneath had supported all three the whole time. Nobody had gotten around to it.
Stage one: a page that feels like a desk
The first release rebuilt the layout — status strip, phone stage, a resizable split, and a
dock — and replaced the input path with a coalesced pointer sequencer: a promise chain paired
with requestAnimationFrame batching, so bursts of movement collapse into the frames that
actually get sent, instead of one WebSocket write per pixel.
The first version of the new stage did not survive first contact with design feedback. It read as, in the exact words we got back, "a black box with a sticker on it" — a dark rectangle with a rendered phone glued to it, all UI widget and no object. We rebuilt it to feel like a phone actually sitting on a lit desk: a light background, a bezel and rim with real depth, a soft cast shadow. Small thing, but it's the difference between looking at your phone and looking at a screenshot of a diagram of your phone.
We verified the whole surface by hand on a real device before calling it done — tap, wheel scroll, swipe, keyboard including accented characters, backspace, right-click for the back gesture, notification shade, screenshot, recording, quality switching, fullscreen, the eight-minute session-refresh boundary, shell access, the Appium endpoint, a full DeviceyAI run, and Stop. The only thing we couldn't verify was clipboard paste, because Chrome gates it behind a permission prompt that doesn't play well with automated checks.
Stage two: giving the dock something to hold
The second release filled in the dock: an Automation tab with hosted Appium connection snippets and a live "verify this endpoint actually works" check, the CLI connector command per shell, streamed Espresso test runs, and a table of sessions that are genuinely open right now, not just ones we think should be. Alongside it, a Logcat tab, and an Inspector overlay that lets you pick a real UI element off the running screen and get back a usable locator.
Both the Inspector and the Automation tab needed to answer a harder question than "is the phone reachable": is a WebDriver session against this rental actually live, ended, or forcibly revoked. We proved that end to end against production — a laptop's WebDriver session showed up as Live within twenty seconds of connecting, a client-side disconnect flipped it to Ended, and stopping a rental out from under an open session produced a revocation record with a reason attached, and the Pi actually killed the connection rather than leaving it dangling.
Stage three: the bug that only showed up once we compared two tools
The Inspector scales a tap correctly: the screen dump comes back in the phone's native resolution, the visible mirror is usually smaller, and the Inspector does the arithmetic to convert one to the other before sending the tap. DeviceyAI, our automation copilot, didn't. It had been built assuming the two were the same size.
They aren't. On the default quality preset, the device reports 1080×2400 native pixels while the mirror actually renders at 720×1600. Every tap DeviceyAI issued landed 1.5x further from where it meant to go, and anything past the mirror's own edge was rejected outright — which meant the bottom and right thirds of every screen were simply unreachable to the AI, silently, with no error surfaced anywhere.
The fix was to stop letting two features maintain their own copies of the same math:
// src/lib/device/frame.ts
treeFrame(dump) // native pixel bounds from the raw screen dump
scaleToFrame(x, y, from, to) // shared coordinate conversion
orientationDiffers(a, b) // catches a rotated frame before scaling garbage
Both InspectorProvider.tapNode and DeviceyAI's tap tool call the same three functions now.
We watched it work on hardware: a tap that used to be flatly refused now opens Chrome from the
dock, on the very preset that used to reject it.
Swipe deliberately stayed unscaled — input swipe takes device pixels natively, so scaling it
would have introduced the bug it was trying to fix. Its bounds check just needed to move from
checking against the mirror to checking against the actual screen.
There was a smaller, funnier bug found in the same pass: the tap "ripple" animation DeviceyAI draws on the mirror was immortal. The device context object driving it was rebuilt fresh on every render, so an effect that keyed its cleanup on that object tore down and restarted constantly — which meant the timer meant to fade a ripple out never got to run. Ripples piled up on the mirror forever, one per tap, until the page reloaded. The fix was to stop subscribing to the whole object and subscribe to the one stable callback it exposes instead.
What three staged releases bought us
Each release only started once the previous one had been driven by hand on a real phone, not just passed its test suite. That's slower than shipping all three at once, and it's also why the tap-scaling bug got caught by comparing two features side by side instead of shipping silently broken to customers — the Inspector existed first, doing the math correctly, which gave us something to notice DeviceyAI wasn't doing the same thing.
The page that came out the other end doesn't look dramatically different in a screenshot. It just finally behaves like something a person is using, instead of something a dashboard is glancing at.