The check-in button opened a transaction form: how three device-QA bugs made bb-finance's Home gamification navigable

The streak block on the Home screen had a “Check in” button. Tapping it opened the transaction form.

Not the check-in flow. Not a confirmation sheet. The transaction form — the same screen that appears when you tap the + button to log a spend. That was device-QA bug #2 from the AnF round; bug #1 was the Quests block (tapped it, nothing), and bug #3 the Level block (same). Three gamification blocks on the Home screen, and none of them were doing what their presence implied.

The intent was there in the design. The routing wasn’t.

What the blocks were doing

At the point the AnF round surfaced these, each block was pulling real state. Streak count, level XP, and quest count were all live. The data was correct. The blocks just didn’t navigate anywhere, and the one that had a button sent you somewhere wrong.

Tappable UI that doesn’t tap is a specific kind of wrong. It isn’t a crash. It isn’t missing data. It’s the gap between what a block implies and what it delivers: a user forms an intent, acts on it, and the app ignores them. Device QA surfaces this reliably. Static testing doesn’t, because static testing doesn’t form intent.

The wrong routing on the Streak check-in was the most disorienting of the three. An inert block is at least honest about its absence of function. A button that routes you to the wrong place is actively misleading: you tap check-in, expect a check-in, get a transaction form. The mismatch is jarring in a way that mere inertness isn’t.

Phase N

Phase N closed all three bugs. The work divides into two parts: making the blocks navigable, and making navigation land correctly.

Each block now routes to a dedicated detail screen. Tapping Quests opens a quest list: active quests, claim state, progress toward each. The Level block opens an XP breakdown with the current level, progress toward the next, and recent contributions. Streak opens a detail screen where the check-in action sits in context.

The check-in now checks in. Fixing the routing was straightforward once the destination existed. The earlier problem wasn’t a wiring error; it was that no destination had been designed, and the button had been wired to the nearest plausible substitute.

The detail screens matter beyond the bug fix. Home blocks are compressed representations by design: a count, a progress bar, a number. The detail screen is where that compression unfolds into something actionable. A block without one behind it is a summary of nothing. You can see this clearly in retrospect: the Streak block’s data was correct, but the check-in button was the only affordance it offered, and it went nowhere useful. Phase N makes a detail screen structurally required for anything that ships on the Home gamification rail. Destination first, block second.

The side-branch work in the same PR

PR #10 carried Phase N alongside several phases developed in parallel on a side branch. Three of them touch the gamification layer directly.

Phase C: QuestGenerator. The mockQuests array is gone, replaced by QuestGenerator, a pure type that emits up to four quests per day from real transaction history and claim state. One of the new quest types is categoryBudgetQuest: fires when any spending category has three or more outflow transactions in the last seven days with total spend at or above £20. It selects the category with the highest spend; the player sees a specific, grounded target rather than a generic challenge. The generator is pure: same inputs, same quests, every time.

Phase D extension: streak milestones. StreakMilestones is a pure type with a single method: crossed(previous:current:) -> Int?. Pass the streak count before and after a check-in; get back the highest milestone crossed, or nil. Milestones sit at 7, 14, 30, 60, and 100 days. The celebration overlay fires when the method returns a non-nil value. Flame-themed. Proportionate to the milestone.

PE-005: daily save target. UserProfileStore gains dailySaveTarget: Decimal, a published property with default £15, persisted as a Decimal-string in UserDefaults. This closes PE-005 and wires the SaveX quest to a real per-user preference rather than a hardcoded constant. Dynamic Type clamp prevents the setting label from overflowing at large accessibility sizes.

Out of scope: quest claim animations, cross-device streak persistence, server-side timestamp validation for check-ins. All deferred.

What the QA round revealed

Device QA has surfaced gamification-layer issues twice now: earlier rounds caught rendering defects in the transaction list, the AnF round caught interaction defects in the Home blocks. The pattern: unit tests and snapshot tests find logic errors and layout regressions. Device QA finds interaction contracts that were never written down.

The Streak check-in routing to the transaction form wasn’t a standard bug. The code wasn’t doing something different from the spec; the spec was silent on routing. The block existed; its navigation target was assumed. Device QA made the assumption visible by doing what a user does: tap the thing and expect it to work.

A unit test for “tapping the Quests block navigates to quest detail” would have caught bug #1 before it shipped. No such test existed because the detail screen hadn’t been designed when the block shipped. The test couldn’t precede the realisation that the destination was missing. Device QA preceded it.

Interactivity as a structural contract

The practical outcome of Phase N is a constraint: a gamification block on Home now implies a detail screen. Not as a guideline. As a structural requirement. Without one, the block is decoration. Decoration that looks interactive is a specific kind of wrong, as bugs #1 through #3 demonstrated.

The check-in button is the clearest case. An inert block is incomplete; it leaves the user with nowhere to go. Wrong routing is worse: it sends them somewhere they didn’t ask to go. Inertness doesn’t generate false confidence. Misrouting does.

Phase N fixes both. But the constraint it establishes is about the second case. If a block ships with an action trigger, the action has to exist and route correctly before the block ships. Intent and routing specified together, not sequentially.

The AnF round enforced that. Three blocks looked done. The gamification data was live and correct. Three taps proved otherwise.

All writing