š§ Work We Couldn't Afford
āWeāve decided to fall back to the common web version of the whiteboard on all platforms for release,ā my skip-level manager said.
I was on Zoomās native client team, working on the native version of that whiteboard. Some platforms still werenāt at feature parity, and closing the gap would have taken too many engineering hours. Even with Zoomās resources, that was enough to change the product we built.
I saw the same constraint in our day-to-day work on the native client. We supported iOS, Android, macOS, Windows, and Linux, and allocated our time according to usage and product priorities. In the work I was involved in, macOS and iOS received a lot of attention. Linux felt like a second-class platform. Five platforms was a short enough list to write on a whiteboard, but we still couldnāt give all of them the same care.
A few years later, I was at a startup event hosted by SURF Incubator in Seattle, telling someone that Iād started bringing my iOS apps to Android. They asked whether I was using Flutter. It was a reasonable assumption: one developer, multiple platforms, presumably a shared codebase to keep the work manageable. But I was having AI models help me build native Android versions.
My apps are much smaller than Zoomās collaborative whiteboard. Still, I find the change striking. Work that I once would have struggled to justify taking on by myself has become a reasonable addition to my workload. It makes me wonder how many decisions about what software should look like were really decisions about how much work we could afford.
Sharing a codebase is one way to reduce that workāitās the same move as the common web whiteboard. Frameworks like Flutter let you carry more of an implementation across platforms, which can make an otherwise unmanageable project viable. You also take on the frameworkās choices and constraints. Depending on the application, that can be a trade youāre happy to make. But Iām finding that AI gives me more room to consider separate native implementations, too.
I can see this even more clearly in ControllerKeys, my app for using game controllers to control a Mac. There are a lot of controllers people want to use, and supporting them well means accounting for their differences. For one developer, that can become a daunting amount of work. Yet much of what the app needs to do is the same regardless of which controller someone owns.
A button needs to trigger an action. A stick needs to provide movement. Different devices expose those inputs in different ways, but once I translate them into something the app understands, the rest of the app can use them. That translation is an adapter, and it gives me a useful way to break down what otherwise looks like the enormous task of supporting everybodyās hardware.
AI has made working through those adapters much more practical for me. Customers send feedback and bug reports. I investigate their setups, work through fixes, and improve the app. Each report gives me one particular difference to understand. With AI helping, I can take on more of these cases without each one eating such a large share of my time.
That feedback loop matters because supporting a controller is an ongoing commitment. Generating an adapter is a start; I still need to know whether it behaves properly and whether a change breaks something else. Hardware access, useful diagnostics, and customers who can describe or test a problem all help close that loop. The easier it is to observe whatās happening, the more useful the AI becomes.
Linux was the platform that got the least care at Zoom. These days I have a laptop that sits even further down that kind of priority list: a 2020 13-inch Intel MacBook Pro running Omarchy. Apple never supported Linux on it, and only a sliver of Linux users own one. Still, Touch ID works, and so does the Touch Bar, thanks to the people who did the hard part: the t2linux community, the t2touch project for Touch ID, and the Touch Bar work my setup builds on.
I took it a step further and wrote my own Touch Bar program, with animationsāsome of which react to the sound coming out of the speakers. You can see them in motion in this Instagram reel.
My custom Touch Bar on Linux: a strip of controls over an animated sunset.
No company would staff that project. The audience is me. But it has the same shape as a controller adapter: a specific piece of hardware, a behavior I want, and a way to check the resultāin this case, by looking down at the keyboard. With the machine in front of me, I can try something, see what happens, and try again. A few years ago, Iād have filed this under things Iād do if I ever had the time. Now itās running on my desk.
The phrase I keep coming back to is: if itās enumerable, itās possible. Iām using āenumerableā loosely hereābeing able to list the cases doesnāt guarantee that I can finish them, and technically an enumerable list doesnāt even have to be finite. What interests me is the growing range of problems where identifying the variations and being able to test them gives me a practical way to work through them.
At Zoom, we already knew the list of platforms. The list wasnāt the hard part. Every item represented engineering time we had to find, including all the work that would arrive after the first version shipped. With ControllerKeys, Iām finding that the cost of handling another variation has fallen enough to change how many I can support. The list is becoming something I can work through instead of an inventory of everything I donāt have time to do.
There are limits. Controllers multiplied by firmware versions, connection modes, and operating-system versions can still add up to an unpleasant number of cases. Some failures are difficult to reproduce, and some platform restrictions wonāt yield to more code. A native Android app also needs to keep working as both the app and Android change. I still have to choose what to take on and verify the result.
But those choices look different when the work costs less. A small group of users can become worth supporting. An unusual controller can become worth investigating. A separate native implementation can fit into one personās workload. That widens the range of products a solo developer can reasonably build and maintain.
At Zoom, the answer to a parity gap was to fall back to the version every platform could share. I still make that call sometimes. But more often now, I can just close the gap: build the Android version natively, write the adapter for the odd controller, make the Touch Bar on a Linux MacBook do what I want. I still get messages from people whose controllers donāt work the way they expect. More and more, I have the time and tools to do something about it.
"We've decided to fall back to the common web version of the whiteboard on all platforms for release," my skip-level manager said.
I was on Zoom's native client team, working on the native version of that whiteboard. Some platforms still weren't at feature parity, and closing the gap would have taken too many engineering hours. Even with Zoom's resources, that was enough to change the product we built.
I saw the same constraint in our day-to-day work on the native client. We supported iOS, Android, macOS, Windows, and Linux, and allocated our time according to usage and product priorities. In the work I was involved in, macOS and iOS received a lot of attention. Linux felt like a second-class platform. Five platforms was a short enough list to write on a whiteboard, but we still couldn't give all of them the same care.
A few years later, I was at a startup event hosted by SURF Incubator in Seattle, telling someone that I'd started bringing my iOS apps to Android. They asked whether I was using Flutter. It was a reasonable assumption: one developer, multiple platforms, presumably a shared codebase to keep the work manageable. But I was having AI models help me build native Android versions.
My apps are much smaller than Zoom's collaborative whiteboard. Still, I find the change striking. Work that I once would have struggled to justify taking on by myself has become a reasonable addition to my workload. It makes me wonder how many decisions about what software should look like were really decisions about how much work we could afford.
Sharing a codebase is one way to reduce that workāit's the same move as the common web whiteboard. Frameworks like Flutter let you carry more of an implementation across platforms, which can make an otherwise unmanageable project viable. You also take on the framework's choices and constraints. Depending on the application, that can be a trade you're happy to make. But I'm finding that AI gives me more room to consider separate native implementations, too.
I can see this even more clearly in [ControllerKeys](https://kevintang.xyz/apps/controller-keys?utm_source=kevin-md&utm_medium=post&utm_campaign=work-we-couldnt-afford-202611), my app for using game controllers to control a Mac. There are a lot of controllers people want to use, and supporting them well means accounting for their differences. For one developer, that can become a daunting amount of work. Yet much of what the app needs to do is the same regardless of which controller someone owns.
A button needs to trigger an action. A stick needs to provide movement. Different devices expose those inputs in different ways, but once I translate them into something the app understands, the rest of the app can use them. That translation is an adapter, and it gives me a useful way to break down what otherwise looks like the enormous task of supporting everybody's hardware.
AI has made working through those adapters much more practical for me. Customers send feedback and bug reports. I investigate their setups, work through fixes, and improve the app. Each report gives me one particular difference to understand. With AI helping, I can take on more of these cases without each one eating such a large share of my time.
That feedback loop matters because supporting a controller is an ongoing commitment. Generating an adapter is a start; I still need to know whether it behaves properly and whether a change breaks something else. Hardware access, useful diagnostics, and customers who can describe or test a problem all help close that loop. The easier it is to observe what's happening, the more useful the AI becomes.
Linux was the platform that got the least care at Zoom. These days I have a laptop that sits even further down that kind of priority list: a 2020 13-inch Intel MacBook Pro running [Omarchy](https://omarchy.org). Apple never supported Linux on it, and only a sliver of Linux users own one. Still, Touch ID works, and so does the Touch Bar, thanks to the people who did the hard part: the [t2linux](https://wiki.t2linux.org/) community, the [t2touch](https://github.com/macintog/t2touch) project for Touch ID, and the Touch Bar work my setup builds on.
I took it a step further and wrote my own Touch Bar program, with animationsāsome of which react to the sound coming out of the speakers. You can see them in motion in [this Instagram reel](https://www.instagram.com/reel/Dd5PmFSs_QL/).

*My custom Touch Bar on Linux: a strip of controls over an animated sunset.*
No company would staff that project. The audience is me. But it has the same shape as a controller adapter: a specific piece of hardware, a behavior I want, and a way to check the resultāin this case, by looking down at the keyboard. With the machine in front of me, I can try something, see what happens, and try again. A few years ago, I'd have filed this under things I'd do if I ever had the time. Now it's running on my desk.
The phrase I keep coming back to is: if it's enumerable, it's possible. I'm using "enumerable" loosely hereābeing able to list the cases doesn't guarantee that I can finish them, and technically an enumerable list doesn't even have to be finite. What interests me is the growing range of problems where identifying the variations and being able to test them gives me a practical way to work through them.
At Zoom, we already knew the list of platforms. The list wasn't the hard part. Every item represented engineering time we had to find, including all the work that would arrive after the first version shipped. With ControllerKeys, I'm finding that the cost of handling another variation has fallen enough to change how many I can support. The list is becoming something I can work through instead of an inventory of everything I don't have time to do.
There are limits. Controllers multiplied by firmware versions, connection modes, and operating-system versions can still add up to an unpleasant number of cases. Some failures are difficult to reproduce, and some platform restrictions won't yield to more code. A native Android app also needs to keep working as both the app and Android change. I still have to choose what to take on and verify the result.
But those choices look different when the work costs less. A small group of users can become worth supporting. An unusual controller can become worth investigating. A separate native implementation can fit into one person's workload. That widens the range of products a solo developer can reasonably build and maintain.
At Zoom, the answer to a parity gap was to fall back to the version every platform could share. I still make that call sometimes. But more often now, I can just close the gap: build the Android version natively, write the adapter for the odd controller, make the Touch Bar on a Linux MacBook do what I want. I still get messages from people whose controllers don't work the way they expect. More and more, I have the time and tools to do something about it.