Live
Throwing away four months of my own work — and why it was right
Rackd is a tournament platform where nobody has to install anything. Two discarded attempts as a Flutter app came first. On 11 June 2026 I wrote down the reasoning for abandoning my own codebase and starting over. This case study is about the decision, not the result.
Visit the projectVerified numbers
- Flutter files discarded at the decision
- ~90Source: docs/03-strategy-decision.md, 11 Jun 2026
- Commits in the rebuild
- 384Source: git log rackd, 19 Mar 2026 to 10 Jul 2026
- Commits in the discarded predecessor
- 52Source: git log pongPartner, 14 Feb 2026 to 16 Mar 2026
- Lines of TypeScript
- 33,452Source: Repository excluding dependencies, as of 11 Aug 2026
- Documented decision phases before the rebuild
- 6Source: docs/01-analysis.md through 06-roadmap.md
Starting point
Beer pong tournaments are still organised on paper or in spreadsheets. Two of my own attempts as an Android app already existed — a discarded skeleton and a substantially developed app with match tracking, early tournament features and a fully designed mechanism for merging results played without an account into a real account later.
Outcome
A running platform where tournaments are played through a shared link — no installation, no account for participants. The tournament logic lives in its own package with no runtime dependencies and enforced coverage thresholds, and database access is denied by default and opened deliberately.
Three attempts, two in the bin
October 2025 — a Flutter skeleton. One commit, then abandoned.
February to March 2026 — pongPartner, 52 commits. A real app with match
tracking, early tournament features and a worked-out concept for attributing
results played without an account to a real account later. Far enough along to
keep going.
June 2026 — instead of continuing: six documents. Assessment, market research, strategy decision, requirements, architecture, roadmap.
What the research showed
Two findings tipped the direction.
First: pure score trackers have a demonstrable ceiling. Comparable apps do not get past a few hundred downloads. That is a category problem, not an execution problem — the app is needed once a year at a party and uninstalled afterwards.
Second: the usage sits in tournament organisation. Whoever runs a tournament has a real, recurring problem: twenty people, a bracket, constant questions about the score. The score tracker is the layer that brings people back — but it is not a product on its own.
From that came the product thesis that determined everything else:
“The tournament runs itself, and nobody has to install anything.”
Why that thesis ruled out the app
The core is the shared link. A participant gets a message, taps it and is in the tournament — no store, no download, no account. A spectator opens the same address on the TV in the room.
That is unremarkable on the web and a contradiction in an app. Every route to building it with an installed app runs through a barrier that deters exactly the people you are trying to reach.
At that point the decision stopped being a matter of taste. The decision document lists the cost explicitly — in substance: roughly 90 files of Flutter code get discarded, but the interface needed redesigning anyway, there were no tests, and the tournament engine needed rewriting regardless.
That is the sentence that matters. Not “the old code was bad”, but a sober account of what is lost — and the observation that most of it would have been incurred anyway.
What survived from the predecessor
Not the code, but the thinking: the data model, the rules for match progression
and above all the mechanism for merging a player account with already-played
matches. That was fully designed in pongPartner and carried over unchanged.
So four months of work did not vanish. What vanished was the implementation — the part that comes back fastest once the concept stands.
How the rebuild is built
The tournament logic lives in its own package with no runtime dependencies. It is the one place where a bug destroys a running tournament, so it is tested completely, without a database, without a browser, with coverage thresholds enforced in configuration.
Database access is denied by default and opened per table deliberately. Writes touching several rules at once run through functions with fixed privileges rather than direct table access.
The application runs in Docker on its own server behind Caddy with automatic TLS, shipped through GitHub Actions. German and English are equal from the start — not retrofitted.
Why this is here
This case study has the weakest user numbers of any on this site and is still the one I care about most.
Clients rarely buy a finished feature. They buy someone who notices when the chosen route does not reach the goal, and who says so before the money is spent. I made that call against my own work and documented it. Against someone else’s plan it comes easier.
Frequently asked
- When is a rebuild better than continuing?
- When the reason sits in the product rather than the code. Here it did: the core idea — tournaments through a link, no installation — is native to the web and a contradiction inside an app. If only the code had been messy, cleaning it up would have been the right answer. The honest test is: does the problem disappear if I tidy the code? If not, rebuilding is the cheaper option.
- What is not lost in a decision like this?
- Almost everything except the code. The data model, the rules engine and the mechanism for merging accountless results into a real account later were already worked out in the predecessor and carried over unchanged. What was discarded were roughly 90 files of interface and integration code — the part that would have been rewritten anyway.
- Why is the tournament logic its own package?
- Because it is the one place where a bug destroys a running tournament. As a standalone package with no runtime dependencies it can be tested completely without a database, browser or network — and the coverage thresholds are enforced in configuration, not recommended.
- What does default-deny database access mean?
- That no table is readable or writable on its own and every access has to be granted explicitly. The reverse approach — open everything, lock specific things down — reliably ends with one table forgotten. Writes that touch several rules at once run through functions with fixed privileges rather than direct table access.