DONTHI.DEV
HAM--:--:--
← ALL DISPATCHES

DISPATCH · TV PLATFORM · 6 MIN READ

Sixty Frames on a Cabin TV

xtv-platform is the TV side of what we build at Xcontrol: the guest-facing apps running on the panels in cabins and hotel rooms. Channel rails, program guides, service menus, the interface a guest meets when they pick up the remote. Under the vendor wrappers they're web apps, and for years they were built the way web apps are built: components rendering into the DOM.

On a developer laptop that's fine. On the actual panel it never quite was. Rails stuttered when you scrolled them. Remote presses landed with a visible delay. Boot was long enough that guests pressed the button twice. Nobody had filed a ticket called "the platform feels cheap", and no migration was on the roadmap. I put it there.

The problem was the DOM itself

A hospitality panel is not a laptop. It's a low-power SoC running a Chromium build that's often years old, with a fraction of the CPU and memory a phone gets. Every DOM mutation drags the full pipeline behind it: style recalculation, layout, paint, composite. A poster rail with a few dozen tiles animating at once asks that pipeline to run per frame, and the panel simply can't. You can virtualise, you can throttle, you can will-change your way to small wins, and we did. The ceiling stays where the DOM put it.

Lightning Blits takes the pipeline out of the equation. It's the app framework on top of Lightning 3, the open-source renderer Comcast built for exactly this class of device. Components are declarative templates with reactive props, so the developer experience feels familiar, but nothing renders to the DOM. The whole UI is a WebGL scene graph: textures on a canvas, animated on the GPU. No layout, no reflow, no style recalculation, ever.

Nobody asked for a prototype, so I built one

Arguments about frameworks go nowhere; demos on real hardware end them. I took our heaviest screen, the channel rail with live poster art, and rebuilt it in Blits. Same data, same design, same panel. Then I put the two versions side by side in front of the team and handed someone the remote.

The DOM version did what it always did: dropped frames on every scroll, hesitated on every keypress. The Blits version tracked the remote like a native app. The numbers, measured on the panel and not on anyone's laptop:

METRIC              DOM APP          BLITS PROTOTYPE
cold boot           ~8 s             ~2.5 s
rail scroll         janky            steady 60 fps
JS heap after nav   ~190 MB          ~75 MB
MEASURED ON THE TARGET PANEL, SAME DATA, SAME SCREEN.

That meeting ended with the migration on the roadmap. Not because anyone loved the idea of rewriting working screens, but because the gap was too large to defend keeping.

Widgets first, screens second

A migration where every developer learns WebGL texture management from scratch dies in the first sprint. So before any screen moved, I built the widget kit: the reusable Blits components that encode the hard-won TV knowledge once, so building a screen becomes assembly.

With the kit in place the migration ran as a strangler: screen by screen, new Blits screens shipping alongside remaining DOM ones, always a rollback path. Each converted screen inherited the performance profile for free, because the widgets carry it.

Measured where guests actually watch

Every number above came off real panels, not desktop Chrome, and staying honest about that is its own discipline. This is the same itch that later became tv-mcp: the loop of installing on a physical TV, screenshotting it, reading its console, and evaluating state inside the running app, without walking to the screen. Measure on the device the guest holds the remote for. Desktop numbers on TV projects are fiction.

What the migration taught me

If you're building UIs for TVs, set-top boxes, or anything else where the hardware fights back, I'd be glad to compare notes: email or LinkedIn.

← ALL DISPATCHES OPEN A CHANNEL →