← ALL PATCH NOTES

Going paid + the dead-button massacre

v0.4.0 NewFix
  • Paid-tier foundation: roles, team management with multi-seat admin, and a mobile gate that captures your email instead of serving you a broken layout.
  • An automated UI audit found 40 dead buttons and links. All 40 now do something or are gone. If it renders, it works.
  • Session redirects fixed: page routes send you to sign-in instead of a JSON error nobody asked for.
THE BUILD LOG

We wrote a program to find the buttons that did nothing

Six hundred and eighty-four lines of static analysis, whose only job was to catch us shipping furniture. Then a fix that lasted thirty-eight minutes.

There is a specific kind of dishonesty that creeps into software nobody has charged for yet. You build a page, you lay out the controls, and one of them is going to need a backend that does not exist. So you leave the button there. It looks right. It is, you tell yourself, a placeholder.

On the evening of 2026-05-06 we went through the entire application and counted them. The count was bad enough that the fix took three separate waves and ran until midnight.

The first wave was the honest work: wiring eleven dead buttons on the database and prospect pages to handlers that actually did something. Nine hundred and fifty-one lines. That is the version of this story where everyone comes out looking competent.

Then, at 19:58, instead of continuing to find them by hand, we wrote a program to find them for us.

The robot that audits your own lies

scripts/ui_wiring_audit.py is 684 lines. Its entire purpose is to walk the project's own templates looking for controls that do not lead anywhere. Buttons with no handler. Links pointing at routes that do not exist. Form submissions into the void.

We committed it, and then we committed its output, which is a decision I still find funny. There is a JSON report, a markdown punch list, and a plain text file that is literally the program's console output, checked into version control. Over the next two days that report got regenerated and re-committed seven separate times, which is what happens when you build a machine that grades your homework and then keep doing homework.

It found things. Of course it found things.

The third wave started at 23:49 and lasted four minutes, and every commit in it is net negative. The community page gave up eleven lines and gained nothing at all, a commit with zero insertions. Analytics lost three "coming soon" placeholders. Outreach lost a templates panel and an A/B testing panel in two lines.

The totals look bigger than that, and it is worth saying why, because the honest number is smaller than the flattering one. Across those four minutes the diff shows 281 deletions, but 252 of them are inside the audit robot's own regenerated report. Only about twenty-nine lines of actual interface were removed. Deleting a dead button is usually a one-line job; what makes it feel like an achievement is finding it.

None of it was building. All of it was removing things that had been quietly implying capability. A billing panel with no billing behind it. A database export that exported nothing. Guild features for guilds that did not exist yet.

The decision rule we landed on, and still use: a control that cannot do its job gets deleted, not disabled and not left looking functional. A greyed-out button says "not for you." An absent button says nothing, which is the honest amount to say about a feature you have not built.

Thirty-eight minutes of bulletproof

The best thing that happened that night was a mistake, and it is preserved in the log with unusual clarity.

At 23:05 we shipped a batch of newly wired modals. At 23:11, six minutes later, some of them were not firing, so we shipped a fix whose subject line opens with the word "Bulletproof" and goes on to promise inline onclick handlers for the two buttons in question. Inline handlers. Attributes on the elements themselves. The oldest trick available, reached for because it was 23:11 and it works.

At 23:44 we found something else. The login decorator had been answering HTML page requests with a JSON 401. Not a redirect to a sign-in page. A raw JSON error, delivered to a browser expecting a document. Which means some of the buttons we had spent the evening classifying as dead were not dead at all. They were hitting an authentication wall that replied in the wrong language, and every symptom looked identical to a missing handler.

Five minutes after that, at 23:49, we shipped a commit whose subject line reads: "Replace e30a04f inline-onclick band-aid with event-delegation system."

That is the hash of the commit from 23:11. The word "bulletproof" had a shelf life of thirty-eight minutes, and the commit that killed it named its victim in the subject line. The replacement is a proper delegation system: one listener, reading an attribute off whatever got clicked, which survives the page being rebuilt underneath it. The commit body is honest about the residue it left behind, noting that a couple of global shims stayed in place so any straggling inline calls would keep working until the next sweep.

I want to defend the 23:11 commit for a moment, because "we shipped a band-aid and ripped it out" reads as a failure and was not one. At 23:11 the diagnosis was wrong: we thought the handlers were not binding. Inline attributes were a reasonable test of that theory and they took two minutes to write. The theory turned out to be half right, and finding that out cost less than designing the correct system would have. The mistake was not the band-aid. The mistake would have been keeping it.

Why any of this happened on the day we went paid

All of this landed on the same day the paid tier did, and the ordering is not a coincidence.

Earlier that evening, in a 61-minute stretch, the application grew an entire commercial layer: roles, token authentication, webhooks, team management, a public REST API with a published spec, an enrichment waterfall across five vendors, and credits to pay for it. The plan for it was 866 lines of design document, committed before a single line of implementation, and the implementation that followed was about 6,100 lines.

There is a detail in there worth more than the feature list. Every one of those phases landed almost entirely outside the main application file. Phase one added four new modules and touched the monolith for 26 lines. The public API phase added six files and touched it for seven. After three months of everything living in one enormous file, the paid tier was the first thing built as though it would need to be maintained by someone who had not written it.

And once you are asking people for money, the placeholder buttons stop being placeholders. They become a description of what somebody just bought. A free tool with a dead export button is a project. A paid tool with a dead export button is a different word.

So we deleted them, and we built a robot to keep finding them, and the robot has been grading us ever since.

The number that matters from that night is not the eleven buttons we wired or the 281 lines we deleted. It is that we now have a program that will tell us, automatically and without sympathy, every time we do it again.

— DealArena Team