The best thing I shipped this week wasn't a feature. It was a sentence in the README: true wireless is impossible, and this tool doesn't pretend otherwise.
Everything else in the build — eight packages, 190 passing tests, a demo video that took two tries to get right — would have been worthless next to that sentence, because the industry standard right now is the opposite: demos that quietly crop out everything the product can't do, and README files written like ads. I decided to run the experiment the other way around. Publish the limitations first, features second. See what survives.
The demo that told the truth
The project is PhoneHands, an open-source iPhone automation tool — one API engine and one device engine, with automatic fallback between them. Here's what the build actually looked like, without the cropping:
- 190 tests, all green, across eight packages. Coverage between 91 and 98 percent. Not "thoroughly tested" — 190 green, a number you can count.
- The CI pipeline failed repeatedly on both platforms before it passed — three rounds on iOS, three on Android. Each failure was fixed in one segment and re-verified in that segment before touching the next — a hardcoded bundle ID on iOS (install succeeded, launch found nothing), a missing system library on the Android runner (the emulator couldn't even boot), and runner permissions that blocked hardware acceleration. Incremental verification, small fixes. No hero commits.
- The demo video's first version got rejected — by me — for a black first frame and a look that wasn't good enough. The second version is a real recording of the real chain: three API reads, a 403, the automatic downgrade, twenty matches read back from the device in four milliseconds. Nothing is simulated except the framing.
And the README carries the honesty corner: true wireless — phone in your pocket, no computer nearby — is a wall Apple built into iOS. No software on this repo gets through it, and no marketing copy here claims otherwise. What the tool does promise, it promises at the operational extreme.
The unsolved-problems list as acceptance criteria
Here's the mechanism, because principles without mechanics are slogans. Before the MVP counted as "done," every unsolved problem in the category had to be named and closed or explicitly admitted:
- Physical reach. USB tethering is the only reliable path, so a one-command remote topology script ships in the repo — admit the constraint, then engineer around it, don't market through it.
- API fragility. Unofficial APIs die without warning, so the router downgrades to the device engine automatically — proven live when a 403 flipped the path mid-run. The fallback isn't a roadmap item. It fired.
- Signing tax. iOS resigning every seven days on free accounts is documented in the repo, not buried in an FAQ on page three.
- Self-drawn UIs. Apps like WeChat that paint their own pixels get a screenshot-fallback chain, not a shrug.
- Credentials. Passwords and verification codes stay in a handoff protocol — the tool never asks for, stores, or echoes your secrets.
- Cloud relays. Data crossing someone else's server is a subscription fee and a privacy question, so the relay is self-hosted.
Six problems, six answers or honest admissions. That list is the real deliverable. The code is just the evidence that the list was taken seriously.
Why "fake it till you ship" is a tax, not a strategy
The opposing religion says: ship the happy path, collect stars, fix the edge cases when someone complains. It feels fast. It is fast — at generating the specific kind of technical debt that erodes trust in developers first and users second. Every cropped-out limitation is a support ticket that hasn't been filed yet. Every "coming soon" that was never coming is a maintainer who stops trusting their own README.
The honest version is slower on launch day and faster on every day after. You never have to un-claim anything. Your issue tracker fills with bug reports instead of betrayal reports — a much better class of problem. And there's a compounding effect nobody talks about: when you're honest about six problems, the seventh one gets found by someone else, because you've trained your users to look for the list. I checked the name, by the way — the first two candidates collided with existing projects, so the project got renamed to PhoneHands before anything went public. Naming honesty starts with not taking someone else's name.
Built for one builder first
I'm building for one user before I build for everyone: me. PhoneHands exists because I needed to automate a phone without lying to myself about what the phone allows. That N-of-one discipline is what organized this whole series — the right to refuse, the Tuesday audit, the deletion discipline, the treasurer — and it reaches a new edge here: a builder tool that treats its first user as the QA department. If I can't look at the README and see my own constraints named honestly, it doesn't ship.
The repo is still private, pending the last gate — real-device verification, which needs a hardware budget I'm still working out. The tests are green, the demo is honest, the limitations are published. But mock-passing is not done; a real phone is the only acceptance that counts. Saying "not done" while 190 tests are green is the same muscle as saying "can't do wireless" while the engine works. It's the whole product, actually. The product is the list.
So here's my rule, running in production this week: no feature ships without its limitation published beside it, in the same breath, in the same README. Perfect demos are cheap to make and expensive to believe. An unsolved-problems list is expensive to write and cheap to trust.
Stop shipping perfect demos. Start shipping honest limitations. The demos will be less exciting. The trust will compound.
Top comments (0)