Working on someone else’s product makes it possible to focus on the part you own. Shipping my own app removed that boundary. Every confusing empty state, slow query, privacy sentence, screenshot, build setting, and release decision came back to me. I could no longer call the feature finished because the main screen worked on my phone.
That experience changed how I build for other people. I pay more attention to the edges around a feature: how a person discovers it, what happens when data is missing, and whether the system can recover. Ownership made me less impressed by the center of a demo and more interested in whether the whole path holds together.
For Ordo, the exciting screen was the daily ranking: a small set of choices, a deliberate ordering gesture, and the promise of seeing how your taste compares with friends. The screen was easy to imagine. The product around it was the real work.
A daily ritual needs reliable content, identity, groups, deadlines, history, notifications, privacy, and a release process. It has to feel complete on the first day without becoming repetitive on the thirtieth. It must explain what happens when someone misses a day, joins late, loses a device, or opens the app before friends have submitted.
Start with the loop, not the feature list
The core loop is simple:
- Open the daily topic.
- Rank the items.
- Commit the order.
- Compare with a group and the wider aggregate.
- Return for the next topic.
Every early decision was evaluated against that loop. A feature earned its place if it made the ritual clearer, more social, or more durable. This helped cut attractive distractions. There was no need for a general-purpose feed, direct messaging, or an elaborate avatar economy. Those features would have increased moderation and navigation work without strengthening the ranking moment.
The constraint also shaped the interface. Ranking needed to feel tactile, but submission needed to feel final. Rather than use a generic system alert for every decision, the UI combines clear just-in-time language with a deliberate haptic and settling animation. The interaction communicates consequence without turning the core action into a form workflow.
Design the missing-day experience early
Daily products usually celebrate streaks and completed days. That can make history easy to model as a list of receipts. The problem appears when someone misses a topic and wants to catch up. If history contains only completed entries, the missing day vanishes. The user cannot tell whether there was no topic, the app failed, or they simply did not participate.
Missed days became first-class archive entries. That decision affected data queries, empty states, navigation, and the visual model. A long reverse-chronological list worked for a few weeks but became unreasonable at a year. A month-and-day archive was a better representation because the information is fundamentally temporal.
The calendar is not decorative. It answers three questions quickly: which days had topics, which days were completed, and which missed days remain available. Swipeable month navigation keeps the surface compact, while a day detail can show the topic and the action still available.
This is a useful product lesson beyond games: model absence explicitly when absence has meaning. Omitting a record and representing an uncompleted record are not the same user experience.
Keep content separate from the binary
A daily app cannot depend on an App Store release whenever a topic needs correction or replacement. Content lives in a hosted data source with an editorial workflow, while the app ships the rules for presenting and validating it.
That separation introduces responsibility. Live content needs stable identifiers, publication state, duplicate detection, and a record of what has already appeared. A resolver should not select only from a static pool; it should consider recent history so a technically valid topic does not repeat too soon.
I also keep source material and live state aligned. A content change is not complete merely because a local seed file changed, and it is not maintainable if someone edits only the hosted row. Verification reads the same resolver the app will use after the update.
For group-authored topics, the system needs a stricter boundary. User-generated text is attached to an account and group, passes validation at write time, and remains separate from global editorial content. The product can be playful without making moderation an afterthought.
Privacy is a product feature
Ordo begins with an anonymous account so a person can understand the product before committing personal information. Sign in with Apple exists to protect continuity across devices, not to unlock basic functionality. That means account linking, restoration, and deletion have to work without creating duplicate social identities.
The app avoids advertising and third-party tracking SDKs. Group activity is visible to the group; public comparisons use aggregates rather than names. Those constraints simplify some choices and make others more important. Operational logs must not become an accidental analytics system, and deletion must remove account-owned content through a server-authorized path rather than trusting the client.
App Store privacy work is easier when the architecture already has clear data purposes. The privacy worksheet then describes real behavior instead of forcing a late audit of unknown SDK collection.
Test the state machine, then test the feeling
SwiftUI makes it fast to assemble a screen, but the durable logic belongs in deterministic state transitions. Ranking edits, submission, reveal availability, archive states, and retry behavior can be exercised without a network or simulator when reducers and repositories have clear boundaries.
The broader test pyramid includes:
- unit tests for ordering, eligibility, date boundaries, and state transitions;
- repository tests for mapping hosted records into domain models;
- UI tests for ranking, archive, group, and account flows;
- rendering checks across compact and large devices;
- release-mode builds with production entitlements and configuration.
Tests do not replace visual review. A calendar can be technically responsive and still feel cramped. A submission transition can preserve state and still feel too light for an irreversible action. I use simulator captures and TestFlight as review surfaces after the deterministic gates pass.
Testing on more than one device size caught assumptions hidden by a favorite simulator. Long labels, accessibility text, and sheet dismissal all behave differently when vertical space is constrained. The correction was not to add special cases everywhere; it was to make the layout communicate priority more clearly.
Treat release engineering as product work
The path to TestFlight includes bundle identifiers, signing, entitlements, privacy URLs, review notes, screenshots, account-deletion behavior, and a build number that always moves forward. None of those steps improve the ranking algorithm, but each can stop a real user from reaching it.
I keep release instructions close to the project and automate the repeatable parts through native build tooling. The release gate builds the exact scheme and configuration that will ship. If signing fails, the diagnosis distinguishes a code problem from a local keychain or profile problem instead of changing project settings blindly.
External testing also needs a populated review path. A social app that opens to an empty group is difficult to evaluate. Reviewer notes and seeded review content should demonstrate the real loop without granting broad production access or requiring personal coordination.
The important mindset is that upload success is not the finish line. A build must finish processing, appear in the intended testing group, satisfy export compliance, and be installable by the expected tester. Each stage has a different source of truth.
A narrow product can still be deep
The best decisions came from protecting the core ritual. The archive exists because daily participation creates meaningful gaps. Hosted content exists because a daily product must evolve without binary releases. Privacy choices exist because comparison should not require surveillance. Deterministic state exists because social and network timing create many plausible partial states.
The result is not a collection of unrelated infrastructure. It is the support system that lets one small interaction remain clear every day.
What I would carry forward
Define the repeatable loop before the feature list. Model missed participation explicitly. Separate editorial content from the app release while keeping both sources verifiable. Make privacy decisions early enough to simplify the architecture. Test state deterministically, review the feeling on real devices, and treat the path from archive to installable TestFlight build as part of the product itself.