Everything Would Have Kept Working

The page I deleted had been alive for three days. It also held the only control anywhere in the app for turning notifications on and off, and notifications were staying.

I found that in the diff. Nothing else in the project was ever going to tell me.

Five notices, one page

The page and the notification system were born in the same commit, at 09:25 on 9 August. That commit is where push.js, calendar.js and the page component all first appear, and its subject line names the page.

The same commit wired five places that send a notification. One of them belonged to the page. The other four had nothing to do with it. Thirty-two minutes later an unrelated feature commit added a sixth, also not the page’s.

So there was never a version of this app in which notifications were a feature of that page. From the first commit they were five-sixths general infrastructure with one local customer. What made them look local is that the log records them arriving under the page’s name, and the switch was on the page’s screen.

I wrote both halves that morning, and named the commit after the smaller one, because the smaller one was why I had sat down.

Senders are cheap, switches are not

A sender is a function call. Six of them across a 2,480-line server file, three or four lines each, dropped wherever the event happens. Nothing constrains where a call goes, and nothing needed renaming as they spread.

A switch is a place. It needs a spot in a layout, next to something a person is already looking at, and there has to be exactly one of it, because a second on/off control for the same thing is a bug. So the capability propagated at the speed of adding a function call, and the control stayed at the address where it was first typed.

That asymmetry is nobody’s mistake. Code can be everywhere at once; an interface is a scarce location, and the two spread at very different speeds.

76 of 146 lines

The page was 146 lines. Seventy-six of them were the switch: a base64 helper for the server key, two pieces of state, the effect that reads the current subscription, enablePush, disablePush, four buttons, a line explaining that some browsers cannot do this at all, and an error line.

The feature the page was named for was the other seventy.

One more thing lived there. navigator.serviceWorker.register('/sw.js') appears exactly once in the entire client, inside enablePush. The page held the app’s only registration of its service worker, buried in a function about a settings toggle, because that is where it had first been needed.

What the clean delete would have looked like

Suppose I had removed the page without reading closely.

The server keeps subscriptions in a file of its own, so every device already subscribed stays subscribed. /api/push/subscribe and /api/push/unsubscribe stay routed. The client’s subscribePush and unsubscribePush stay exported, and nothing calls them. Five senders keep firing.

A phone with notifications on would keep receiving them, indefinitely, with no way to turn them off. A phone without them would have no way to turn them on. The subsystem would be complete, correct, running, and permanently unreachable.

Nothing fails in that story. There is no import error, because nothing imported the deleted component. There is no broken route, because the route was removed on purpose. There is no red test: the suite is four files, all of them server-side library logic, and not one of them touches notifications. The build is clean and the app boots.

And the notifications keep arriving. That is the part worth sitting with. The evidence in front of me would be a working notification on my phone, which reads as confirmation that I did the deletion correctly.

Dependencies announce themselves. Addresses do not.

Deleting things feels safe because breakage is loud. You cut something, the things that needed it cannot find it, and you know within seconds.

Every one of those signals is produced by a dependency. Nothing depended on that page. Something merely lived on it.

A dependency is a claim one file makes about another, and cutting it leaves a visible hole at both ends. Sharing an address makes no claim. It is a fact about layout, recorded nowhere except in the layout, and it survives exactly as long as the layout does. A deletion’s blast radius is measured by what was standing inside the thing, not by what was pointing at it.

Which is why removing a feature is when a codebase’s oldest accidents come due. Everything ever put somewhere for convenience is sitting in a room built for another reason, and the deletion is the first event that ever asks about the arrangement.

Where it lives now

Seventy-six lines came out and went into components/PushToggle.jsx, ninety-five with the wrapper around them. It is mounted in the header of the maintenance page, on the reasoning that the maintenance page sits nearest to the things the notifications are about.

The whole record of where the switch lives is two lines in that page: an import at the top, and <PushToggle /> after a &middot; in the header row.

That is the same amount of anchoring the last one had.


← all writing