Blink's tab bar is going away. In its place: a zoom-stack, three levels deep, that you push into and pull back out of instead of hopping between tabs. It's a better shape for what the app actually does. It's also the kind of change that, done wrong, reads to a tester as "the app broke overnight."
Real people use Blink every day right now. Not a lot of people, but enough that I can't just take the app down for two weeks, gut the navigation layer, and put it back together. Whatever I do has to happen underneath users who didn't ask for a redesign and shouldn't have to notice one.
Neither classic option survives contact
The obvious move is to refactor in place: nudge the current tab bar toward the zoom-stack a little at a time, PR by PR, until one day it's fully transformed. I tried sketching this out and it falls apart on a simple fact: a tab bar and a zoom-stack aren't points on the same line. There's no halfway state where three tabs are "kind of" a stack of three zoom levels. You can't gradually morph one into the other any more than you can gradually morph a bicycle into a boat. Every intermediate commit is either the old thing or a broken thing, and testers hit the broken thing.
The other obvious move is greenfield: branch off, build the new shell properly, cut over when it's done. This throws away something I'm not willing to throw away: a year of plumbing that's been hardened against real usage. Sync edge cases, offline states, calendar quirks, all the stuff that only shows up once actual people touch it for months. A clean rebuild means re-discovering every one of those the hard way, and it means giving up the thing I actually want most going into a rebuild this size: the ability to flip one switch and be back on the version that works, immediately, no rollback deploy required.
So: strangler fig
The new shell grows inside the same binary as the old one, behind a feature flag. The old UI stays the default for every tester. Nothing about their day-to-day changes. Meanwhile, in the same codebase, the zoom-stack gets built out screen by screen, level by level, entirely inert unless the flag is on.
The rule that makes this survivable is on the backend, not the UI: it only ever adds. No endpoint change is allowed to break what the old client expects, full stop. I enforce that with a compatibility test that does something almost embarrassingly literal. It runs the actual old app against the actual new backend, on every PR. If a backend change would strand the shipping app, the test fails before the change merges. It's not a policy anyone has to remember to follow. It's a build that won't go green if you break it.
CI builds and boots both UIs on every PR, old and new, so a change that quietly wrecks the zoom-stack gets caught the same day it's written, not three weeks into the rebuild when nobody remembers which commit did it. New work lands on an integration branch that pulls from trunk daily, so the new shell never drifts far enough from reality to become its own science project.
The bill for all this is carrying two UIs at once for however many weeks the rebuild takes. More code paths, more surface area, a slightly heavier CI run. I'm fine paying it, because the alternative bill (a redesign that visibly breaks for testers mid-refactor, or a big-bang cutover with no way back if it's wrong) is worse and it's due immediately, not amortized.
One demolition instead of a slow bleed
The plan ends with a single deliberate step: flip the flag, watch it hold, then delete the old shell in one clean pass. Not a slow bleed of half-migrated screens and dead code nobody's sure is safe to remove. One demolition, on purpose, when the replacement has already proven itself under real load with a kill switch sitting right there the whole time.
Here's the part that actually changed how I think about this: I'm not the one writing most of this code. Blink's rebuild, like most of what I ship now, gets built by a fleet of autonomous agents working in parallel. Eight sessions at a time isn't unusual. And eight parallel agents cannot be trusted, individually or collectively, to not break trunk. That's not a knock on the agents; it's just true of eight independent actors touching a shared system fast. The old assumption behind "just be careful" was that a human was doing the careful part. That assumption is gone.
Which means the actual engineering problem isn't "write the zoom-stack correctly." It's "make it structurally impossible for any of the eight to break the trunk, so their mistakes get caught by CI instead of by testers." The compatibility test, the dual-boot CI, the flag, the daily sync from trunk, none of that is about this rebuild specifically. It's the scaffolding that lets me point a fleet of agents at a live product at all. The code is replaceable. The safety structure around it is the actual product of this phase of work.