| raw | raw/this-simple-feature-almost-broke-my-app.md |
|---|---|
| url |
TL;DR: Luna’s “accounts” feature looked simple until it touched account types, starting balances, reconciliation, credit cards, transfers, transaction creation, widgets, and the app’s core promise of simplicity. Requested features can be correct and still threaten what makes the product work.
Biggest lessons
- Simple features hide domain complexity. Bank accounts and credit cards use opposite balance logic; “add an account” was not one data field.
- Starting state matters. Users do not begin at zero, so accounts needed starting balances and later manual reconciliation.
- Transfers are a new product model. Paying a credit card is not income or expense, so the app needed a third transaction type that touched multiple screens and data structures.
- Every added field increases friction. The transaction screen kept growing: type, recurring settings, account, transfer source, transfer destination. More correctness made the app less approachable.
- Protect the original product promise. Chris slowed down because Luna’s core advantage was simplicity. Shipping the feature badly could have damaged the reason users liked the app.
Why it matters
- This source is core app-product-craft: the craft is knowing when a valid feature request adds too much complexity.
- It connects to product-led-growth because product-led apps need simplicity to keep self-serve activation working.
- It is a useful counterweight to vibe-coding: AI can accelerate implementation, but it does not remove domain modeling and UX tradeoffs.