Dev Diary

Making recommendations easier to trust

INCREMNT is an iOS strength training app that explains what to lift next; this week brought clearer recommendation reasoning, privacy-safe analytics and tighter production tooling.

  • Workout recommendations gained clearer explanations of where a target came from, whether it still fits, and why INCREMNT is suggesting a change.
  • A stale programme slot can now use newer, compatible history for the same exercise instead of blindly repeating an old target.
  • Athletes can choose whether progression recommendations apply automatically, wait for confirmation, or stay off.
  • Analytics coverage expanded across exposure, intent and outcome signals without collecting raw prompts, notes, errors or stable account identifiers.
  • Subscription and Strava work moved closer to external review, while production tooling gained narrower Sentry triage and an encrypted, restore-tested database backup path.

Product: a recommendation should explain itself

A number like “70 kg × 8” looks precise. That doesn’t make it trustworthy.

INCREMNT recommends what to do next by looking at previous training and the prescription attached to the current programme slot. The awkward case is when the same exercise appears in several places. Monday’s bench press slot might be stale even though Thursday’s version contains newer, useful evidence of what the athlete can do.

This week I built a more careful fallback for that situation. The progression system can gather contextual evidence for an exercise, compare compatible slots, and prefer the newest relevant capacity instead of automatically reusing the current slot’s old target.

Compatibility matters. A target from a different number of working sets isn’t automatically equivalent. A recommendation can become stale after time away. A recovery-week prescription shouldn’t escape into an ordinary training week. The fallback respects those boundaries before treating another slot as useful evidence.

The visible explanation now follows the same reasoning. Recommendation popovers lead with what happened, explain why it matters, and finish with the available action. The logger can distinguish between increasing weight, building reps, repeating a target, going lighter and starting from compatible history. When INCREMNT adjusts a target, it can say so instead of presenting the new number as though it had always been prescribed.

The goal isn’t to sound more certain. It’s to expose enough reasoning for the athlete to make the call.

Progressive overload now has three controls:

  • Apply automatically keeps recommendations inside the normal workout flow.
  • Ask before applying shows the recommendation but leaves the change to the athlete.
  • Off hides recommendations and falls back to recent completed work.

The explanation view links directly to that setting. Changing it doesn’t rewrite completed workouts or exercises the athlete has already edited. Recommendations should help with the next decision, not quietly revise the record underneath it.

Also moved forward

Subscription work connected RevenueCat access state to the iOS app and hosted capabilities. I also added a trial-first Pro path and instrumentation for onboarding and paid acquisition. External configuration and review checks were still unresolved in the week’s release notes, so this remains launch preparation rather than a claim that the full path was publicly available.

The Strava integration moved closer to developer review. Connection and revocation now pass through the hosted service, the settings screen uses Strava’s approved connect treatment, and the privacy and review documentation better matches the implementation. This was submission-readiness work. It wasn’t proof of approval.

Workout finalisation on Apple Watch also received attention. The changes addressed finalisation races, preserved prescribed rest durations, and made discarded or orphaned Watch workouts terminate cleanly. These aren’t glamorous fixes, but they protect the training record that later recommendations rely on.

I also tightened several smaller interaction points. Recommendation badges remain readable in the compact workout header. Feed cards and Score calendar markers respond from the areas people naturally tap. Increment Score no longer gets overwritten by an unsettled HealthKit value during launch.

Building INCREMNT: measure the decision, not the content

The largest engineering problem this week was analytics coverage.

INCREMNT contains plenty of data that would be convenient to capture and irresponsible to place in general product analytics. A Coach request contains a prompt. A training note contains free text. An imported workout contains exercise names and loads. A failed integration may expose a detailed error object.

The easy implementation is to send whatever object already happens to be in scope. That’s also how an analytics system becomes a shadow copy of the product’s most sensitive content.

I took a narrower approach. Meaningful product capabilities now declare which parts of a simple lifecycle they need:

  1. Exposure: was the relevant surface available?
  2. Intent: did the athlete attempt the meaningful action?
  3. Outcome: did it complete, fail, get denied or get cancelled?

The event carries the decision state around the content, not the content itself. A failure becomes a bounded category instead of a raw error. A flow reports its step and outcome instead of the values entered into it. A Coach event can record its entry point and latency without sending the question or response.

A checked-in coverage registry makes those decisions reviewable. Each capability declares whether it requires an exposure, intent or outcome signal, where that signal lives, and which source evidence proves it exists. By the end of the work, the registry covered all 114 lifecycle checkpoints marked as required.

That figure is an instrumentation-coverage count, not a usage metric. It doesn’t claim the product is perfectly understood. It means the repository can test whether every required checkpoint has an implementation.

The validator also rejects sensitive property categories, including raw errors, messages, notes, prompts, text, URLs and stable user or account identifiers. Onboarding and subscription events use the same typed layer, which makes questions such as “where did setup stop?” or “did the purchase attempt complete?” answerable without collecting the contents of someone’s programme, Coach conversation or training note.

There’s a real trade-off here. Bounded analytics events are worse at reconstructing one mysterious session after the fact. I’m comfortable with that. Product analytics should reveal patterns across flows. Technical diagnostics belong in Sentry, AI traces belong in the tracing system, and neither should be silently copied into PostHog.

The part worth stealing for your own project: make the privacy boundary executable.

A policy document can say “don’t collect sensitive text.” A checked-in schema and validator can stop it from happening. Future features now have to make an explicit instrumentation decision instead of relying on somebody remembering the rule during review.

The same preference for narrow, explicit boundaries showed up in the development tooling. A new external-tool router lets operational checks select the service they need instead of probing every connected system. Sentry triage gained a focused path for listing and inspecting unresolved issues. That reduces ambient tool traffic and makes failures easier to understand without requesting unrelated credentials.

The backup path became concrete too. The sync service now has a daily PostgreSQL workflow that validates a dump, encrypts it before upload, and keeps the private decryption identity separate. The runbook records a successful restore into a disposable database, verification of aggregate counts, and removal of the temporary copy. A backup isn’t proven by the upload. It’s proven by the restore.

Tiny wins

  • Isolated Xcode package caches by checkout while keeping them reusable across focused verification runs.
  • Added clearer TestFlight checks for subscription, Sentry and release configuration.
  • Split the sync service’s large route owner into domain-focused modules without changing its public API.

What’s next

The next work is likely to stay close to recommendation correctness: how an explanation leads into a choice, and how that choice carries into the next workout.

The new analytics coverage should make those flows easier to evaluate without weakening the product’s privacy boundaries. Subscription and integration paths still need external review and release evidence before I’ll describe them as finished.

This Dev Diary was generated from the week’s commits, pull requests, tests and development notes using AI, then reviewed and edited before publication. It may describe work that was built during the week but not yet released publicly.