fitme·story
v5.0 · 4 min read
Summary card · 60-second read

SettingsView v2 — 1170 → 294 Lines via Phased Decomposition

Version
v5.0
Date
2026-04-19
Tier
light

Closing audit finding UI-002: SettingsView.swift dropped 1170 → 294 lines (~75%) across 4 PRs (#122-#125) in a ~2-hour single-session decomposition. Pure structural refactor — zero new code, zero behaviour change, 9 visibility bumps, 8 new files. Second feature to ship concurrent case-study tracking from inception (after M-3).

Honest disclosures
  • No manual visual QA across the 5 detail screens. Behaviour preservation verified at the type level (same *SettingsScreen types, same coordinator wiring) but a tap-through pass would catch subtle SwiftUI rendering regressions if any.
  • Pre-existing EncryptionService + KeychainHelper test failures (9 tests) reproduce on clean main. Not introduced by M-1; same simulator-keychain environment friction as M-3.
  • CI billing block in effect during M-1 — PRs #122/#123/#124 hit the GitHub Actions account spending limit; admin-overridden the merges. Same infrastructure friction since Sprint K.
  • The 294-line coordinator still embeds summaryBadges() helpers (~50 lines) and deep-link routing destinations. UI-002 is closed at the file-size layer; finer extraction (M-1e) would be taste, not audit.
  • M-1c was a judgment call: SettingsView.swift was 401 lines after M-1b (one over the 400 target). The right call was a clean coordinator at 294 lines, not "fail the threshold by one line". Audited as decision D-3 in the source case study.
How to read this case studyT1/T2/T3 · ledger · kill criterion
T1Instrumented
Numbers come from a machine-generated ledger or commit. Reproducible. Highest reader trust.
T2Declared
Numbers stated by a structured declaration (PRD, plan, frontmatter) but not directly measured.
T3Narrative
Estimates and observations from session memory. Useful for context; not citable as evidence.
Ledger
Where to verify the claim — a file path, GitHub issue, or backlog entry. Anything labelled ledger: is the audit trail.
Kill criterion
The pre-registered threshold under which this work would have been killed mid-flight. Not fired = work shipped without hitting the threshold.
Deferred
Items intentionally not closed in this version. Each cites the ledger that tracks remaining work.
Pre M-1 (1170 lines)
1 monolithic file
Coordinator + 5 detail screens + 4 scaffolds + 5 form components + 4 home views, all in SettingsView.swift. UI-002 audit finding open.
Post M-1d (294 lines)
1 coordinator + 8 files
5 screens in `Settings/v2/Screens/`, 3 component bundles in `Settings/v2/Components/`. Coordinator routes only. UI-002 closed.

SettingsView v2 — 1170 → 294 Lines via Phased Decomposition

M-1 closed audit finding UI-002 ("SettingsView.swift, 1170 lines, too many responsibilities"). The 1170-line file was already well-structured internally — five private struct *SettingsScreen: View blocks called from a navigationDestination switch — so the work was mechanical-but-careful: extract each block to its own file, bump private → internal so the coordinator can still reach them, add a Screens PBXGroup to project.pbxproj. Same again for shared scaffolds + form components in M-1b, and home-view sub-components in M-1c. Pure structural refactor; zero behaviour change.

What shipped

PRPhaseWhatLines moved
#122M-1a5 detail screens → Settings/v2/Screens/521
#123M-1bScaffolds + form components → Settings/v2/Components/248
#124M-1cHome-view sub-components → Settings/v2/Components/SettingsHomeViews.swift107
#125M-1dCase study + monitoring entry + UI-002 closure

Each PR was a single-direction structural change. Could revert any one without losing the others. Build + (Settings-related) tests green at every PR.

Numbers — before and after

MetricBefore M-1After M-1aAfter M-1bAfter M-1c
SettingsView.swift lines1170649401294
Files in Settings/v2/1689
New PBXGroups01 (Screens)2 (+Components)2

What worked

  • Plan doc with line-level inventory before starting. docs/superpowers/plans/2026-04-19-m1-settings-v3-decomposition.md mapped every section of the 1170-line file to either "Stay" or "Extract → file". Each of the 4 phases shipped within ±50% of estimate.
  • The original file was already well-structured. The 5 detail screens were 54-155 line private struct blocks with @EnvironmentObject deps. No interfaces had to be redesigned.
  • Sequential phases isolate risk. Each PR was a single-direction structural change; rollback is surgery, not amputation.
  • Visibility bump is a one-line cost per dependency. 9 visibility bumps across 3 PRs. Each was a 1-character delete (private); no fan-out.
  • Case study tracking concurrent with feature (second time). M-1d ships in the same session as M-1a/b/c. M-3 was the first feature to do this; M-1 confirms the pattern is reproducible.

What broke down

  • Pre-existing test infrastructure failures (EncryptionService + KeychainHelper) on the simulator's keychain — verified pre-existing via git stash + re-run. Same environmental friction as M-3.
  • No manual visual QA across all 5 detail screens. Type-level behaviour preservation only.
  • CI billing block continues — admin-overridden merges. Not an M-1 issue.

Lessons

  • Mechanical extraction is fast when the source already has clear seams. The 5 detail screens already lived as private structs with @EnvironmentObject deps. The work was moving files + visibility bumps + pbxproj edits — no interface redesign. 0.5 audit findings/hour vs. 1.2/hour for M-3 (which was content-additive); pure structural work pays a project.pbxproj tax that content-additive work skips.
  • One-line judgment calls don't deserve over-decomposition. M-1c was 1 line over the 400-line threshold after M-1b. A 100-line extraction "for the sake of 1 line" would have been overkill if the resulting coordinator wasn't the right size. Decision: extract the home views as a single file (conceptually a unit) and land at 294 lines. Right call documented; not a process violation.
  • Concurrent case-study tracking is now reproducible. M-3 was the first feature to ship a case study in the same session as the feature itself. M-1 is the second. Going from "1 of 1 tries succeeded" to "2 of 2" elevates the pattern from anecdote to default.

Links