Skip to content

feat(ios): sync design system and app screens to code - #23

Merged
johnnyhuy merged 6 commits into
mainfrom
feat/design-sync-to-code
Sep 28, 2026
Merged

johnnyhuy merged 6 commits into
mainfrom
feat/design-sync-to-code

Conversation

@johnnyhuy

Copy link
Copy Markdown
Contributor

What

Syncs the iOS app to the BetterFit design system established in Figma, and ships the reference material alongside it. This is the "app screens → code" pass.

Why

The Figma work (design system + competitor research + remixed app screens) was the source of truth for the new direction. The code still carried the old ember-orange / multi-accent palette and the pre-ledger layouts. This brings the two into agreement.

Changes

Design tokens

  • Two-colour identity: electric yellow #FFD60A as the only chromatic accent, black ink, neutral structure — replaces the ember-orange and multi-accent palette.
  • Yellow ladder (bright / deep / dim) so status and recovery ride brightness plus words rather than competing status hues.
  • Glass tokens for floating chrome only, plus borderStrong and accentText (deep yellow on light fields for AA contrast).
  • Typography: BBH Hegarty display ramp, Inter UI ramp, JetBrains Mono tabular numerals.

Ledger layout

New BFLedger primitives used across the rebuilt screens: eyebrow, section rule, spec strip, slab, readout, bar, ledger row, quick actions.

Screens

  • WorkoutHome — Plan/ledger layout: plan exercise rows with inline weight/reps editing, superset pairing, equipment swapping.
  • ActiveSession — rest bar, quick actions, set logging, session ordering, supersets.
  • Profile / Recovery / RootTab / SignIn / Settings — rebuilt to the new tokens.
  • New: Log tab, Targets tab, workout summary screen.

Competitor-informed patterns

Borrowed from the Fitbod / Strong / Hevy / Freeletics research on the Competitor Research page:

  • Exercise thumbnails + completion state in the work list (Fitbod)
  • Rest surfaced as an action bar, not a separate screen (Fitbod)
  • Single hero on home rather than a feed (Freeletics)

Reference material

  • design.md — the two-colour discipline and its rationale
  • design-system/ — tokens, core/list/data/feedback/workout components, guidelines, and iOS/watchOS UI kits

Code organisation

Split oversized views into extension files per AGENTS.md:

  • WorkoutHomeView → +Sections / +Helpers
  • ActiveSessionView → +Sections / +Actions
  • ExerciseDetailSheet → +Sections

Renamed single-char identifiers and replaced large tuples with named structs.

Verification

Check Result
swift build pass
mise run test 95/95 pass
mise run ios:build:ci pass
SwiftLint serious (introduced by this PR) 0

Notes for reviewers

  • Lint: 12 SwiftLint serious violations remain, all pre-existing on main in files this PR does not touch (HealthKitComponents, AppSearchView, TrendsView, StreakSummarySheetView, SupabasePersistenceService, BetterFitWatchApp, two test files). CI runs build / test / ios:build:ci and does not gate on lint. Happy to clear those in a follow-up.
  • Base branch: the previous local branch (fix/ci-workflows) was a rewritten duplicate history with no merge base to main, so this is branched fresh off origin/main — the diff here is exactly this work and nothing else.
  • design-system/ui_kits/ios_app/v1/*.bak are leftover backup files and are intentionally not committed.

johnnyhuy and others added 6 commits September 27, 2026 00:14
Bring the iOS app in line with the BetterFit design system established
in Figma, and ship the reference material alongside it.

Design tokens
- Replace the ember-orange / multi-accent palette with the two-colour
  identity: electric yellow (#FFD60A) as the only chromatic accent, with
  black ink and neutral structure.
- Add a yellow ladder (bright/deep/dim) so status and recovery ride
  brightness plus words instead of competing status hues.
- Add glass tokens for floating chrome only, plus borderStrong and
  accentText (deep yellow on light fields for AA).
- Typography: BBH Hegarty display ramp, Inter UI ramp, JetBrains Mono
  tabular numerals.

Ledger layout
- Add BFLedger primitives (eyebrow, section rule, spec strip, slab,
  readout, bar, ledger row, quick actions) used across the rebuilt
  screens.

Screens
- WorkoutHome: Plan/ledger layout with plan exercise rows, inline
  weight/reps editing, superset pairing, and equipment swapping.
- ActiveSession: live session with rest bar, quick actions, set logging,
  session ordering, and supersets.
- Profile, Recovery, RootTab, SignIn, Settings, plus new Log and Targets
  tabs and a workout summary.

Competitor-informed patterns (from Fitbod, Strong, Hevy, Freeletics)
- Exercise thumbnails and completion state in the work list
- Rest surfaced as an action bar rather than a separate screen
- Single hero on home rather than a feed

Reference material
- design.md: the two-colour discipline and its rationale
- design-system/: tokens, core/list/data/feedback/workout components,
  guidelines, and iOS/watchOS UI kits

Code organisation
- Split oversized views into extension files per AGENTS.md:
  WorkoutHomeView (+Sections/+Helpers), ActiveSessionView
  (+Sections/+Actions), ExerciseDetailSheet (+Sections).
- Rename single-char identifiers and replace large tuples with named
  structs. All SwiftLint serious violations introduced by this work are
  cleared.

Verified: swift build, 95/95 unit tests, ios:build:ci.

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
The betterfit-* skills read as a separate family next to the unprefixed
project skills (adr-writing, tdd, swift-ios-teaching, qa). The prefix is
redundant inside the BetterFit repo — every skill here is about
BetterFit — so drop it and let them sort as one set.

- betterfit-code-organization   -> code-organization
- betterfit-mise-workflows      -> mise-workflows
- betterfit-supabase-setup      -> supabase-setup
- betterfit-swift-file-splitting -> swift-file-splitting
- betterfit-swiftui-reactivity  -> swiftui-reactivity
- betterfit-testing             -> testing
- betterfit-wide-events         -> wide-events
- betterfit-design              -> design

Update the cross-references in code-organization, mise-workflows, and
remotion-app-showcase that pointed at the old names.

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Two-axis review of the design sync (Standards + Spec) surfaced real
bugs and drift. This fixes them.

Correctness
- WorkoutSummaryView showed fabricated recovery numbers: the summary
  data carried hardcoded Lats/Biceps/Rear-delts defaults that nothing
  ever overwrote. Recovery effects are now derived from the regions
  actually trained and the body map's real status; when there is no
  data the section is hidden rather than invented.
- TargetsView displayed hardcoded Volume 48.2/80k and Time 2.4/4h as
  the user's own stats. Both are now computed from workout history
  (load volume from weight x reps, time from durations). Targets are
  derived from the plan and the trailing 4-week average, and a metric
  with no history renders without a target instead of a fake one.
- ProfileView and AppSearchView were unreachable: the Ledger redesign
  orphaned onProfile/onSearch, which were wired to sheets but never
  invoked. The top bar now carries a Search button and a Profile
  avatar, so both are reachable again.

Design system conformance
- At most one yellow fill per screen (design.md:168): the docked
  primary now honours AppTheme.quietPrimaryAction and renders as
  neutral glass when the screen leads with a yellow field.
- Quick-action strip dropped glass; glass is reserved for nav, dock
  and swipe actions. The superset badge is neutral so it no longer
  competes with the primary action.
- Removed the chromatic .midnight/.forest/.sunset themes, which
  introduced blue/green/red against "do not introduce any additional
  accent hue". Stored values fall back to the default theme.
- Work list rows gained the missing exercise thumbnails.

Documentation and tokens
- design.md still specified the old multi-hue status palette
  (green/amber/red/blue, blue-to-red recovery ramp) while the code
  shipped a yellow ladder. Rewrote both the palette table and the
  summary prompt; synced the design-system mirror.
- Aligned the 15% action tint and the 11px bold eyebrow across
  design.md, the CSS tokens and BFTypography; added the seven type
  sizes typography.css claimed but omitted.
- Documented the real type stack: BBH Hegarty for display when
  licensed, native system for UI copy, monospaced digits for numbers.

Dead code
- Removed no-op controls that lied to the user (Edit, Jump to date,
  Share summary x2), LogView's synthetic heatmap filler and fake
  ledger rows, the unused SupersetStore typealias, and the
  module-wide Double.rounded extension.

Code organisation
- Split BFLedger and ActiveSessionView into focused extension files
  and extracted shared number formatting into BFFormat.

Tests
- Added UI coverage for Log, Targets, Workout Summary and Workout
  Preview, including a guard that the summary hides recovery data
  when there is none.
- The design sync changed the tab set to Workout/Body/Targets/Log;
  the suite still asserted "Plan"/"Me"/"Your Training Plan". Retargeted
  every test to current UI and restored the Profile tests the missing
  affordance had made untestable.

Verified: swift build, 95/95 unit tests, ios:build:ci,
build-for-testing for the UI test target, and zero SwiftLint serious
violations in any file this work touches.

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
The keyboard toolbar was attached inside the exercise-row view, so every
row registered its own Done button. Four exercises meant four buttons
stacked into the keyboard accessory, rendering as overlapping yellow
pills over the tab bar.

Hoist the toolbar to the screen level so there is exactly one:
- WorkoutHomeView commits whichever plan field has focus, via a new
  commitFocusedPlanField() over PlanField.
- ActiveSessionView's commitFocusedField() now resolves the exercise
  from `current` instead of taking it per row.

Verified in the simulator: tapping a weight field shows a single Done,
and tapping it dismisses the keyboard and commits the draft.

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Editing a weight dragged the floating Start workout pill to mid-screen,
where it sat on top of the exercise rows directly above the keyboard.
The dock is an overlay on the whole TabView, so keyboard avoidance moved
it with the content — and a start-workout CTA is not the right affordance
while typing anyway.

Track keyboard visibility from the standard notifications and drop the
dock while it is up, so only the keyboard accessory Done remains.

Verified in the simulator: with a weight field focused the pill is gone
and the rows stay readable; dismissing the keyboard restores it.

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Two display bugs found while walking the app in the simulator.

Body tab: the headline read 100% while every muscle row read 96%. The
headline came from the model's getOverallRecoveryPercentage() and the
rows from RecoveryStatus.bfReadiness (recovered = 0.96), two different
mappings. Aggregate the same per-region readiness the rows render so the
two can no longer disagree.

Log tab: "Time training 1 hours". Pluralise the unit.

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
@johnnyhuy
johnnyhuy merged commit 793e6d6 into main Sep 28, 2026
7 checks passed
@johnnyhuy
johnnyhuy deleted the feat/design-sync-to-code branch September 28, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant