← ALL PATCH NOTES

Deals become first-class citizens

v0.9.0 Rework

The biggest data-model rework of the run so far. If you have ever fought a CRM that thinks a person and a company are the same row, this patch is for you.

  • Prospects and Companies split into separate objects: own cards, own filters, tenant-scoped accounts.
  • A real Deal object with polymorphic tasks. The kanban renders from deals and stage moves persist. Quotes attach to deals. Old pipeline rows backfilled automatically.
  • The /tasks page talks to the real API. The static mock is dead.
  • Log Call modal polish with follow-up tasks, and the new-contact hotkey moved from C to N so it stops eating your copy shortcut.
THE BUILD LOG

We wrote 'NOT yet tested as-deployed' in a commit and were proved right twelve hours later

A kanban board that would not save took three separate root-cause diagnoses across twelve days. Each one explained why the last was wrong.

For most of this product's life, a prospect and a deal were the same row in the same table. That is fine right up until it is not, and the moment it stops being fine is exactly when somebody has two opportunities with the same person, or one opportunity involving four people at one company.

Untangling that happened over two days in June, and the untangling itself is the boring part. Prospects and companies became separate objects on 2026-06-06. Deals became their own first-class object on 2026-06-08, with a table for them, a table linking them to the people involved, and a task system that can attach a task to a prospect, an account, a deal or nothing at all.

The commit that split prospects from companies is 1,307 lines, and about 400 of them are tests. The Gmail work a few days later is past half. That ratio is new for this codebase, which had no tests at all a month earlier, and it climbs exactly where you would expect: on the changes where being wrong is expensive and invisible.

The interesting story that week is not the object model. It is a kanban board that would not save, and the three separate times we were confident we had fixed it.

Three diagnoses, twelve days

The pipeline is a board of cards in columns. Drag a card to a new column and the deal advances. It had been snapping back to its original position, with no save request and no error in the console.

The first diagnosis, on 2026-05-27, produced four fixes, three of them on the save path. The rate limit ceiling was raised, because the application fires six to ten background requests per page load and a user working the board could exhaust the bucket in minutes and get a rejection that looked like a save failure. Response parsing was corrected. An authentication case was handled. The fourth was a rendering function that had been throwing an error on every single page load, which is a different path entirely and was found by accident.

All four were real bugs. None of them was this bug. The commit body calls them "three layered fixes" and then lists four, which tells you how the evening went.

The second diagnosis, on 2026-06-08 at 02:35, opens with the line that reframes everything: every prior fix targeted the save path, which was never reached.

The actual cause was that the drop was never happening. In a browser, dropping something onto an element only works if the element has explicitly said, on every drag-over event, that it will accept the drop. Ours never did. So the browser rejected the drop before any of our code ran, and the card returned to where it came from. Every fix on the save path had been repairing machinery downstream of a door that was closed.

That commit ends with a note I have a lot of respect for: "Verified: delegation mechanism validated live, with the network call stubbed so no production write occurred. NOT yet tested as-deployed."

Eleven hours and forty-four minutes later, on the same day, the third commit explains what happened when it was deployed.

The second fix had attached its listeners to the board element. But the application shell rebuilds the page content on load, which throws away every existing element and parses fresh ones. So the board the listeners were attached to no longer existed by the time a user could touch anything. The commit spells out the consequence precisely: this is why the drag worked in testing, where the code was injected after the rebuild had already happened, and died on every deploy, where it ran before.

The fix is to attach the listeners to the document instead, which cannot be replaced.

That self-flagged caveat is the most valuable sentence in the sequence. Somebody knew, at 02:35, that their verification method did not cover the case that mattered, wrote it down instead of quietly hoping, and by lunchtime had the receipt. The note did not prevent the bug. It made the bug take about four minutes to understand instead of another twelve days.

The day undid its own work

At 22:25 that evening, after a day of building the deal object, a commit landed that is net negative: 190 lines added, 267 removed.

The global "new deal" button, the one in the shell that follows you around the app, was still creating records the old way. So a deal created from that button was written as a prospect, landed on the database grid, and never appeared on the pipeline board, which now reads from the deals table. The commit body notes three separate reported symptoms with one cause, which is the most satisfying kind of bug report to close.

It also removed the Deals navigation item that had shipped about ten hours earlier, leaving the page reachable by URL. Built in the morning, removed the same night, and the page it pointed at kept working.

That is the shape of a migration nobody talks about. The schema change is a day. The long tail is every entry point in the product that still speaks the old language, and you find them by using the thing and being surprised.

There is one commit from that week I want to end on, because it is the opposite of everything above. On 2026-06-07 the application gained distributed tracing: 156 lines added, zero removed, three files. It degrades to nothing if the key is missing, so a missing dependency can never break startup. No fix-ups, no follow-ups, no reverts.

It is the cleanest commit of the month, and it is the one that makes the next month's bugs findable. Which is roughly the deal you get with observability: it never has a good week of its own, and it improves everyone else's.

— DealArena Team