Skip to content
fitme·story
Case studies
v7.9 · 4 min read · 2026-06-02

Exercise Search/Filter — a discoverable read-only library on top of the 50-exercise catalog (C3)

C3 closes backlog L347 — the longest-running "no in-app library" complaint. The training catalog already carried ~50 exercises with rich metadata (category / equipment / muscle groups / coaching cue / sets / reps / rest) but was consumed silently by other screens. C3 ships a discoverable read-only sheet on top of the existing data layer — search + 3 chip dimensions (Muscle / Equipment / Category), no schema change, no data migration. ~1068 LoC across 6 standalone-buildable commits in a single ~2h session. Its picker-mode init signature is the dependency contract that sibling C6 (training program customization) consumes to add exercises.

Before C3
0 discoverable
The ~50-exercise catalog carried full metadata for the entire v7.x window, but only today’s day-locked subset was visible. A user asking "what hamstring exercises does FitMe support?" had no in-app answer.
After C3
~50 searchable
A read-only library sheet on top of the existing data layer — search + 3 chip dimensions (Muscle / Equipment / Category), 4 training_ events, 7/7 tests. No schema change, no migration.
What do T1 / T2 / T3 mean?
T1Instrumented — from a ledger/commit
T2Declared — stated, not measured
T3Narrative — estimate from memory
~50 exercises, finally discoverable · 4 events · 7/7 tests · ~2h
A read-only library sheet on top of a catalog that was already rich but invisible
The training catalog carried ~50 exercises with full metadata for the entire v7.x window, but TrainingPlanView only surfaced today's day-locked subset. A user asking 'what hamstring exercises does FitMe support?' had no in-app answer. C3 adds the library — no schema change, no migration.

Problem framing

TierClaim
T3The training catalog has carried ~50 exercises with full metadata for the entire v7.x window, but TrainingPlanView only surfaces today's day-locked subset. A user wanting to know "what hamstring exercises does FitMe support?" had no in-app answer.
T2Backlog L347 ("Exercise search/filter — fixed order, no search") has been filed since the 2026-04-02 Phase 0 backlog dump (~8 weeks) and reaffirmed during the 2026-04-16 v5.2 stress test.
T1The 2026-05-31 E1 RICE refresh ranked C3 at #4 on the Planned table (RICE 8.0) — high-reach × moderate-impact ÷ low-effort.

What C3 shipped

A discoverable read-only sheet on top of the existing data layer — no schema change, no data migration, ~1068 LoC across 6 standalone-buildable Phase 4 commits.

  • A 7-section frozen PRD locks 7 algorithmic decisions (immediate filter at 50 items, localizedCaseInsensitiveContains against name + muscle groups, 3 chip dimensions, one-of-N-within-dimension AND-across-dimensions chip logic, .large-only detent, 2-char analytics trigger, and the picker-mode init signature).
  • 3 chip dimensions — Muscle / Equipment / Category. The user-facing "Strength" chip is a rollup (machine ∪ freeWeight ∪ calisthenics) expanded in ExerciseLibraryFilter.matchesCategory.
  • 4 new analytics events (screen-prefixed training_): training_exercise_library_opened, training_exercise_search_query, training_exercise_filter_tapped, training_exercise_detail_opened (T1).
  • The picker-mode init signature ExerciseLibraryView(picker: ((ExerciseDefinition) -> Void)? = nil) — the C6 dependency contract. Once C3 merged, C6 could call the library in picker mode without further coordination.

Verification

CheckResult
xcodebuild build (generic iOS Simulator)BUILD SUCCEEDED after every commit (T1)
Filter + analytics tests7/7 PASSED on iPhone 17 Simulator (T1)
make ui-auditP0=0 maintained (1 pre-existing P1 on HRVTrendChart.swift, not C3-touched) (T1)
Tier 2.2 contemporaneous log5 phase_transition entries (T1)

The lifecycle ran Research → PRD → Tasks → Implement → Test — 5 phases in ~3 hours on 2026-06-02 afternoon, mirroring the C5 single-session pattern. Actual LoC (~1068) came in above the PRD's 600 estimate because the chip taxonomy strings were inline and a lightweight FlowLayout helper was added in ExerciseDetailView; wall time (~2h) came in under because no Figma round-trip was needed (AppPickerChip + AppFilterBar already existed).

Coverage scope (honest)

The 7 tests cover all pure-logic surfaces (filter algorithm + analytics param shapes) at >90% on new production files. View-mode behavior (picker vs read-only) and entry-point fire-correct-source tests are deferred — SwiftUI view-level testing without ViewInspector is a known project gap (the parallel-clone simulator hang env-flake makes UI tests high-cost/high-flake). They land in a follow-up once the env-flake root cause is resolved.

What's deliberately not in C3

  • "Add to plan" affordance → C6 (writable surface)
  • Custom UGC exercises → post-launch
  • Image/video demonstrations → future asset pipeline
  • AI-suggested alternatives → D1 (adaptive intelligence)
  • Favourites / sort / multi-select chips / recently-viewed → all future

Phase E discipline

C3 shipped during the v7.9 Phase E 14-day soak (2026-05-21 → ~2026-06-04). No new enforcement gates, no new schema fields, no new observability surfaces — pure consumption of existing v7.8.6 + v7.9 infrastructure. Phase E compliant.

Source case study (FitTracker2): docs/case-studies/exercise-search-filter-case-study.md. Shipped via FT2 PR #573. Kill-criteria first evaluation: T+14d (2026-06-16).

Honest disclosures
  • Test coverage is pure-logic only: the 7 tests cover the filter algorithm + analytics param shapes (>90% on new files), but view-mode behavior (picker vs read-only) and entry-point fire-correct-source tests are deferred — SwiftUI view-level testing is a known project gap (the parallel-clone simulator hang env-flake makes UI tests high-cost/high-flake).
  • The primary metric (library opens ≥0.30 per WAU) is pre-registered with baseline 0 and not yet evaluable — first kill-criteria evaluation is T+14d (2026-06-16), with no live adoption data at ship time.
  • The "~50 exercises were rich but invisible for the entire v7.x window" framing is a T3 narrative claim, not instrumented.
Kill criterion · not fired
  • Library-opened rate at T+14d < 0.05 per WAU (feature unused)
  • Detail-tap-through < 0.10 (browse-only, no engagement)
  • p95 search latency > 100ms on iPhone 17
  • Crash rate on library sheet > 0.5% of opens