Skip to content
All letters

The Dependency That Killed It

A project I had archived came back to life this week. Not by fixing what broke, but by deleting the ambitious part that broke it.

Some months ago I archived a small macOS dictation app. It was a menu-bar utility: press a key, speak, get text. What made it interesting to me at the time was that it also ran a language model to clean up the transcript. Fix the punctuation, drop the "um"s, format the result. That model was the ambitious part. It was also, it turned out, the part that killed the project.

The model ran through an upstream Swift library I depended on. One day that library was deleted from GitHub. No fork I trusted, no drop-in replacement, no path forward that didn't involve me adopting and maintaining an abandoned dependency I never wrote. So I marked the project archived and moved on. The reason I recorded was honest and, I thought, final: the thing it needed no longer exists.

This week I brought it back. And the way it came back is the part worth writing down.

I did not fix the dependency

The instinct, when a project dies because a dependency broke, is to fix the dependency. Fork it, patch it, find its successor, wire in an alternative. That instinct kept me from touching the project for months, because every version of "fix the dependency" was more work than the project was worth, and I knew it.

What I actually did was delete the dependency's entire job. The operating system had, in the meantime, grown native APIs that covered both things the app needed: on-device speech recognition and on-device text formatting. So I rebuilt the app against those instead. The transcription now runs through the platform's own speech framework. The cleanup runs through the platform's own on-device model. The third-party language-model stack, the ambitious part, the part I'd been proud of, the part that broke, is simply gone. Ripped out, not replaced.

The app does the same thing it did before. It does it entirely on-device, with no external library in the critical path, and it is now built on foundations I don't have to babysit because the platform vendor babysits them for me.

The lesson is about what you're proud of

Here is what I had to sit with. The feature I was proudest of was the feature that made the project fragile. The custom model integration was the reason the app felt clever, and it was the single point of failure that took the whole thing down when one repository disappeared. My pride in it was exactly what made archiving feel like a loss instead of a relief.

When I finally came back to it, the move that worked was not "how do I restore the clever part." It was "what if the clever part was never load-bearing." And it wasn't. The user never cared that a bespoke model cleaned their transcript; they cared that the transcript was clean. Once I stopped defending the implementation I was attached to, the platform-native path was obviously better on every axis that mattered, simpler, more private, more durable, and worse only on the axis of my ego.

Resurrection is a form of subtraction

I've written before about the discipline of killing projects, and I still believe most dead projects should stay dead. But this one is a reminder that the reverse move exists and is underused. A project you archived for a specific reason can sometimes be revived not by solving that reason but by removing the part of the project that made the reason matter.

That only works if you're willing to throw away your favorite piece. The version of this app I was attached to is gone for good. The version that's actually installed on my machine and running every day is the one I would have called a downgrade six months ago, fewer moving parts, no custom model, nothing to show off. It is strictly better, and it exists only because the dependency I loved died and forced me to find out I never needed it.

The dependency that killed the project was the same dependency that was holding it up. Removing it was both the funeral and the cure.

Back to all lettersayal.tech / letters