# Polish Enhancements Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Add grouped sorting to trip options and expand planning tips to 100+. **Architecture:** Two independent features: (1) PlanningTips data file with static array and random selection, wired into HomeView; (2) TripOptionsView enhancement to group results by count/range based on sort option. **Tech Stack:** SwiftUI, Swift --- ## Task 1: Create PlanningTips Data Model **Files:** - Create: `SportsTime/Core/Data/PlanningTips.swift` - Test: `SportsTimeTests/PlanningTipsTests.swift` **Step 1: Write the failing test for random tip selection** Create test file: ```swift // // PlanningTipsTests.swift // SportsTimeTests // import Testing @testable import SportsTime struct PlanningTipsTests { @Test func allTipsHasAtLeast100Tips() { #expect(PlanningTips.all.count >= 100) } @Test func randomReturnsRequestedCount() { let tips = PlanningTips.random(3) #expect(tips.count == 3) } @Test func randomReturnsUniqueIds() { let tips = PlanningTips.random(5) let uniqueIds = Set(tips.map { $0.id }) #expect(uniqueIds.count == 5) } @Test func eachTipHasNonEmptyFields() { for tip in PlanningTips.all { #expect(!tip.icon.isEmpty, "Tip should have icon") #expect(!tip.title.isEmpty, "Tip should have title") #expect(!tip.subtitle.isEmpty, "Tip should have subtitle") } } @Test func randomWithCountGreaterThanAvailableReturnsAll() { let tips = PlanningTips.random(1000) #expect(tips.count == PlanningTips.all.count) } } ``` **Step 2: Run test to verify it fails** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/PlanningTipsTests test` Expected: FAIL with "Cannot find 'PlanningTips' in scope" **Step 3: Write the PlanningTips implementation** Create `SportsTime/Core/Data/PlanningTips.swift`: ```swift // // PlanningTips.swift // SportsTime // import Foundation struct PlanningTip: Identifiable { let id = UUID() let icon: String let title: String let subtitle: String } enum PlanningTips { static let all: [PlanningTip] = [ // MARK: - Schedule Timing (~15 tips) PlanningTip(icon: "calendar.badge.clock", title: "Check schedules early", subtitle: "Game times can change, sync often"), PlanningTip(icon: "clock.badge.exclamationmark", title: "Watch for doubleheaders", subtitle: "Two games, one day—great value!"), PlanningTip(icon: "calendar.badge.plus", title: "Add buffer days", subtitle: "Weather delays happen, plan ahead"), PlanningTip(icon: "clock.arrow.circlepath", title: "Account for time zones", subtitle: "West coast games start late back east"), PlanningTip(icon: "tv", title: "Check TV schedules", subtitle: "Nationally broadcast games may shift times"), PlanningTip(icon: "cloud.rain", title: "Rain delay ready", subtitle: "Baseball can push back your whole evening"), PlanningTip(icon: "calendar", title: "Avoid holiday weekends", subtitle: "Hotels and tickets spike on long weekends"), PlanningTip(icon: "sunrise", title: "Day games rock", subtitle: "More time to explore the city after"), PlanningTip(icon: "moon.stars", title: "Night games = city vibes", subtitle: "Experience the stadium lights atmosphere"), PlanningTip(icon: "sportscourt", title: "Weekend series strategy", subtitle: "Catch Friday-Saturday-Sunday for max games"), PlanningTip(icon: "calendar.badge.minus", title: "Check for off-days", subtitle: "Teams travel on Mondays and Thursdays"), PlanningTip(icon: "exclamationmark.triangle", title: "Spring training caution", subtitle: "Split-squad games mean fewer stars"), PlanningTip(icon: "clock", title: "First pitch timing", subtitle: "Arrive 60-90 minutes early for batting practice"), PlanningTip(icon: "hourglass", title: "Game length varies", subtitle: "Baseball: 3hrs, Basketball: 2.5hrs, Hockey: 2.5hrs"), PlanningTip(icon: "arrow.clockwise", title: "Reschedule alerts", subtitle: "Enable notifications for schedule changes"), // MARK: - Driving/Route Tips (~15 tips) PlanningTip(icon: "car.fill", title: "Plan rest days", subtitle: "Don't overdo the driving"), PlanningTip(icon: "fuelpump.fill", title: "Map gas stations", subtitle: "Some stretches are remote"), PlanningTip(icon: "road.lanes", title: "Avoid rush hour", subtitle: "Leave mid-morning or after 7pm"), PlanningTip(icon: "map", title: "Scenic route option", subtitle: "Sometimes the long way is the fun way"), PlanningTip(icon: "bolt.car", title: "EV charging stops", subtitle: "Plan charging during meals for efficiency"), PlanningTip(icon: "car.side", title: "4-hour driving max", subtitle: "More than 4 hours makes for tired fans"), PlanningTip(icon: "location.fill", title: "Download offline maps", subtitle: "Cell service can be spotty in rural areas"), PlanningTip(icon: "gauge.medium", title: "Check tire pressure", subtitle: "Long trips need proper inflation"), PlanningTip(icon: "wrench.fill", title: "Pre-trip car check", subtitle: "Oil, fluids, wipers—be road ready"), PlanningTip(icon: "road.lanes.curved.right", title: "Interstate vs scenic", subtitle: "Interstates are faster but highways are prettier"), PlanningTip(icon: "figure.wave", title: "Stretch breaks", subtitle: "Stop every 2 hours to stay sharp"), PlanningTip(icon: "cup.and.saucer.fill", title: "Coffee shop stops", subtitle: "Local cafes beat drive-thru every time"), PlanningTip(icon: "music.note", title: "Curate your playlist", subtitle: "Great music makes miles fly by"), PlanningTip(icon: "headphones", title: "Podcast the drive", subtitle: "Sports podcasts to get hyped"), PlanningTip(icon: "car.2.fill", title: "Carpool when possible", subtitle: "Split costs and share the driving"), // MARK: - Stadium-Specific (~15 tips) PlanningTip(icon: "star.fill", title: "Mark must-sees", subtitle: "Ensure your favorite matchups are included"), PlanningTip(icon: "fork.knife", title: "Research stadium food", subtitle: "Some parks have legendary eats"), PlanningTip(icon: "camera.fill", title: "Photo opportunities", subtitle: "Scout iconic spots before game day"), PlanningTip(icon: "building.2", title: "Stadium tours", subtitle: "Many offer behind-the-scenes access"), PlanningTip(icon: "figure.walk", title: "Walk the concourse", subtitle: "Each stadium has hidden gems"), PlanningTip(icon: "bag.fill", title: "Clear bag policy", subtitle: "Most stadiums require clear bags now"), PlanningTip(icon: "iphone", title: "Mobile tickets ready", subtitle: "Screenshot or download for offline access"), PlanningTip(icon: "chair.lounge.fill", title: "Seat selection matters", subtitle: "Research views before buying tickets"), PlanningTip(icon: "sun.max.fill", title: "Shade vs sun seats", subtitle: "Day games: pay extra for shade"), PlanningTip(icon: "hand.raised.fill", title: "Foul ball territory", subtitle: "Bring a glove to baseline sections"), PlanningTip(icon: "tshirt.fill", title: "Wear neutral colors", subtitle: "Or proudly rep the home team!"), PlanningTip(icon: "sparkles", title: "Retro stadium charm", subtitle: "Fenway, Wrigley—experience history"), PlanningTip(icon: "building.columns", title: "New stadium tech", subtitle: "Modern parks have amazing amenities"), PlanningTip(icon: "trophy.fill", title: "Championship banners", subtitle: "Look up—history hangs from the rafters"), PlanningTip(icon: "person.3.fill", title: "Fan sections rock", subtitle: "The bleachers have the best energy"), // MARK: - Budget Tips (~15 tips) PlanningTip(icon: "dollarsign.circle", title: "Book hotels early", subtitle: "Game days fill up fast"), PlanningTip(icon: "ticket.fill", title: "Weekday games cheaper", subtitle: "Tuesday-Thursday = better deals"), PlanningTip(icon: "creditcard.fill", title: "Set a daily budget", subtitle: "Stadium food adds up quick"), PlanningTip(icon: "house.fill", title: "Stay outside downtown", subtitle: "Uber in, save on hotels"), PlanningTip(icon: "bed.double.fill", title: "Points and rewards", subtitle: "Hotel loyalty programs pay off on road trips"), PlanningTip(icon: "airplane", title: "Compare fly vs drive", subtitle: "Sometimes flying is cheaper than gas"), PlanningTip(icon: "cart.fill", title: "Grocery runs help", subtitle: "Stock up on snacks, skip concessions"), PlanningTip(icon: "parkingsign.circle", title: "Pre-book parking", subtitle: "SpotHero and ParkWhiz save money"), PlanningTip(icon: "percent", title: "Group discounts", subtitle: "4+ people often get bundle deals"), PlanningTip(icon: "giftcard.fill", title: "Gift card strategy", subtitle: "Buy discounted cards for stadium spending"), PlanningTip(icon: "arrow.down.circle", title: "Last-minute tickets", subtitle: "Prices drop close to game time"), PlanningTip(icon: "arrow.up.circle", title: "Book rivalry games early", subtitle: "Yankees-Red Sox sells out fast"), PlanningTip(icon: "wallet.pass.fill", title: "Season ticket resale", subtitle: "STH often sell below face value"), PlanningTip(icon: "banknote.fill", title: "Cash for parking", subtitle: "Some lots still don't take cards"), PlanningTip(icon: "tag.fill", title: "Student/military discounts", subtitle: "Always ask—many teams offer them"), // MARK: - Multi-Sport Strategies (~15 tips) PlanningTip(icon: "sportscourt.fill", title: "Mix sports", subtitle: "NBA + NHL share many cities"), PlanningTip(icon: "calendar.badge.clock", title: "Season overlap sweet spot", subtitle: "April: MLB + NBA + NHL all active"), PlanningTip(icon: "building.2.fill", title: "Shared arenas", subtitle: "Same building, different sports—efficient!"), PlanningTip(icon: "figure.basketball", title: "Basketball + hockey combo", subtitle: "Many cities host both in one arena"), PlanningTip(icon: "baseball.fill", title: "Baseball road trip season", subtitle: "Summer = the classic sports adventure"), PlanningTip(icon: "hockey.puck.fill", title: "Hockey playoff intensity", subtitle: "April-June games are electric"), PlanningTip(icon: "basketball.fill", title: "NBA in March", subtitle: "Playoff push means high-stakes games"), PlanningTip(icon: "leaf.fill", title: "Fall football overlap", subtitle: "NFL Sundays + college Saturdays"), PlanningTip(icon: "snowflake", title: "Winter sports combo", subtitle: "NBA + NHL peak season in January"), PlanningTip(icon: "sun.min.fill", title: "Spring training + spring break", subtitle: "Arizona and Florida in March"), PlanningTip(icon: "figure.run", title: "Cross-sport rivalries", subtitle: "Same city teams often play same weekends"), PlanningTip(icon: "arrow.triangle.branch", title: "Hub city strategy", subtitle: "Chicago, LA, NYC have multiple teams"), PlanningTip(icon: "globe.americas.fill", title: "Regional concentration", subtitle: "Northeast has tons of teams close together"), PlanningTip(icon: "flag.fill", title: "Border city bonus", subtitle: "Toronto + Buffalo = international trip!"), PlanningTip(icon: "star.circle.fill", title: "All-Star breaks", subtitle: "Avoid—no regular games during break"), // MARK: - Regional Tips (~15 tips) PlanningTip(icon: "cloud.sun.fill", title: "Check the weather", subtitle: "April baseball can be chilly"), PlanningTip(icon: "thermometer.sun.fill", title: "Summer heat warning", subtitle: "Phoenix and Texas get brutal in July"), PlanningTip(icon: "snow", title: "Winter driving caution", subtitle: "Great Lakes region gets snow early"), PlanningTip(icon: "wind", title: "Chicago wind factor", subtitle: "Wrigley wind changes the game"), PlanningTip(icon: "humidity.fill", title: "Southern humidity", subtitle: "Atlanta and Houston: bring water"), PlanningTip(icon: "mountain.2.fill", title: "Denver altitude", subtitle: "Drink extra water at Coors Field"), PlanningTip(icon: "beach.umbrella.fill", title: "California weather wins", subtitle: "LA and San Diego = perfect game days"), PlanningTip(icon: "building.fill", title: "NYC transit tips", subtitle: "Subway to the stadium, skip driving"), PlanningTip(icon: "tram.fill", title: "Boston T access", subtitle: "Green Line to Fenway, easy as pie"), PlanningTip(icon: "car.ferry.fill", title: "Seattle ferry option", subtitle: "Combine a game with Puget Sound views"), PlanningTip(icon: "leaf.arrow.circlepath", title: "Fall foliage bonus", subtitle: "Northeast in October = stunning drives"), PlanningTip(icon: "theatermasks.fill", title: "Local events check", subtitle: "Festivals can spike hotel prices"), PlanningTip(icon: "music.mic", title: "Concert conflicts", subtitle: "Arena shows can affect parking"), PlanningTip(icon: "graduationcap.fill", title: "College town energy", subtitle: "Ann Arbor, Madison—fun atmospheres"), PlanningTip(icon: "flag.checkered", title: "Racing conflicts", subtitle: "Indy 500 time? Indianapolis books up"), // MARK: - Game Day Tips (~15 tips) PlanningTip(icon: "figure.walk", title: "Arrive early", subtitle: "Explore the stadium before first pitch"), PlanningTip(icon: "drop.fill", title: "Stay hydrated", subtitle: "Especially at outdoor summer games"), PlanningTip(icon: "sun.max.trianglebadge.exclamationmark", title: "Sunscreen essential", subtitle: "Day games can burn—protect yourself"), PlanningTip(icon: "battery.100", title: "Charge your phone", subtitle: "You'll want photos and videos"), PlanningTip(icon: "powerplug.fill", title: "Bring a battery pack", subtitle: "Mobile tickets need power"), PlanningTip(icon: "speaker.wave.3.fill", title: "Download team app", subtitle: "In-seat ordering, replays, and more"), PlanningTip(icon: "menucard.fill", title: "Check signature dishes", subtitle: "Every stadium has a must-try item"), PlanningTip(icon: "clock.badge.checkmark", title: "Gates open timing", subtitle: "Usually 90 minutes before first pitch"), PlanningTip(icon: "figure.mixed.cardio", title: "Pre-game neighborhood walk", subtitle: "See the local scene before heading in"), PlanningTip(icon: "wineglass.fill", title: "Pre-game spots", subtitle: "Research nearby bars and restaurants"), PlanningTip(icon: "gift.fill", title: "Bobblehead nights", subtitle: "Arrive extra early for giveaways"), PlanningTip(icon: "pencil.and.scribble", title: "Autograph opportunities", subtitle: "Get there for batting practice"), PlanningTip(icon: "person.badge.plus", title: "Meet other fans", subtitle: "Road trip fans bond fast"), PlanningTip(icon: "heart.fill", title: "Soak it in", subtitle: "Put the phone down sometimes"), PlanningTip(icon: "memoryphoto", title: "Take scorecard notes", subtitle: "Physical mementos beat digital"), ] static func random(_ count: Int = 3) -> [PlanningTip] { Array(all.shuffled().prefix(count)) } } ``` **Step 4: Run tests to verify they pass** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/PlanningTipsTests test` Expected: All 5 tests PASS **Step 5: Commit** ```bash git add SportsTime/Core/Data/PlanningTips.swift SportsTimeTests/PlanningTipsTests.swift git commit -m "$(cat <<'EOF' feat: add PlanningTips data with 100+ tips Adds PlanningTip struct and PlanningTips enum with static array of 105 tips across 7 categories (schedule timing, driving, stadium, budget, multi-sport, regional, game day). Includes random() function for selecting N unique tips. Co-Authored-By: Claude Opus 4.5 EOF )" ``` --- ## Task 2: Wire PlanningTips into HomeView **Files:** - Modify: `SportsTime/Features/Home/Views/HomeView.swift:311-332` **Step 1: Add state property and update tipsSection** In `HomeView.swift`, add a new `@State` property after line 18 (after `selectedSuggestedTrip`): ```swift @State private var displayedTips: [PlanningTip] = [] ``` **Step 2: Replace the hardcoded tipsSection** Replace lines 313-332 (the entire `tipsSection` computed property) with: ```swift private var tipsSection: some View { VStack(alignment: .leading, spacing: Theme.Spacing.sm) { Text("Planning Tips") .font(.title2) .foregroundStyle(Theme.textPrimary(colorScheme)) VStack(spacing: Theme.Spacing.xs) { ForEach(displayedTips) { tip in TipRow(icon: tip.icon, title: tip.title, subtitle: tip.subtitle) } } .padding(Theme.Spacing.md) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.large) .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) } } .onAppear { if displayedTips.isEmpty { displayedTips = PlanningTips.random(3) } } } ``` **Step 3: Run the app to verify tips load** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build` Expected: Build succeeds. Launch app and verify 3 random tips appear in the Planning Tips section. **Step 4: Commit** ```bash git add SportsTime/Features/Home/Views/HomeView.swift git commit -m "$(cat <<'EOF' feat: wire PlanningTips into HomeView - Add displayedTips @State to HomeView - Update tipsSection to use PlanningTips.random(3) - Tips load on first appear, persist during session - Tips refresh only on app relaunch Co-Authored-By: Claude Opus 4.5 EOF )" ``` --- ## Task 3: Add Grouped Sorting to TripOptionsView **Files:** - Modify: `SportsTime/Features/Trip/Views/TripCreationView.swift:1570-1839` - Test: `SportsTimeTests/TripOptionsGroupingTests.swift` **Step 1: Write failing test for grouping logic** Create test file: ```swift // // TripOptionsGroupingTests.swift // SportsTimeTests // import Testing @testable import SportsTime struct TripOptionsGroupingTests { // Helper to create mock ItineraryOption private func makeOption(stops: [(city: String, games: [String])], totalMiles: Double = 500) -> ItineraryOption { let tripStops = stops.map { stopData in TripStop( city: stopData.city, state: "XX", coordinate: .init(latitude: 0, longitude: 0), games: stopData.games, arrivalDate: Date(), departureDate: Date(), travelFromPrevious: nil ) } return ItineraryOption( id: UUID().uuidString, stops: tripStops, totalDistanceMiles: totalMiles, totalDrivingHours: totalMiles / 60, score: 1.0 ) } @Test func groupByCityCountDescending() { let options = [ makeOption(stops: [("NYC", []), ("Boston", [])]), // 2 cities makeOption(stops: [("LA", []), ("SF", []), ("Seattle", [])]), // 3 cities makeOption(stops: [("Chicago", [])]), // 1 city ] let grouped = TripOptionsGrouper.groupByCityCount(options, ascending: false) #expect(grouped.count == 3) #expect(grouped[0].header == "3 cities") #expect(grouped[1].header == "2 cities") #expect(grouped[2].header == "1 city") } @Test func groupByGameCountAscending() { let options = [ makeOption(stops: [("NYC", ["g1", "g2", "g3"])]), // 3 games makeOption(stops: [("LA", ["g1"])]), // 1 game makeOption(stops: [("Chicago", ["g1", "g2"])]), // 2 games ] let grouped = TripOptionsGrouper.groupByGameCount(options, ascending: true) #expect(grouped.count == 3) #expect(grouped[0].header == "1 game") #expect(grouped[1].header == "2 games") #expect(grouped[2].header == "3 games") } @Test func groupByMileageRangeDescending() { let options = [ makeOption(stops: [("NYC", [])], totalMiles: 300), // 0-500 makeOption(stops: [("LA", [])], totalMiles: 1200), // 1000-1500 makeOption(stops: [("Chicago", [])], totalMiles: 2500), // 2000+ ] let grouped = TripOptionsGrouper.groupByMileageRange(options, ascending: false) #expect(grouped[0].header == "2000+ mi") #expect(grouped[1].header == "1000-1500 mi") #expect(grouped[2].header == "0-500 mi") } @Test func groupByMileageRangeAscending() { let options = [ makeOption(stops: [("NYC", [])], totalMiles: 300), makeOption(stops: [("LA", [])], totalMiles: 1200), makeOption(stops: [("Chicago", [])], totalMiles: 2500), ] let grouped = TripOptionsGrouper.groupByMileageRange(options, ascending: true) #expect(grouped[0].header == "0-500 mi") #expect(grouped[1].header == "1000-1500 mi") #expect(grouped[2].header == "2000+ mi") } @Test func emptyOptionsReturnsEmptyGroups() { let grouped = TripOptionsGrouper.groupByCityCount([], ascending: false) #expect(grouped.isEmpty) } } ``` **Step 2: Run test to verify it fails** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TripOptionsGroupingTests test` Expected: FAIL with "Cannot find 'TripOptionsGrouper' in scope" **Step 3: Add TripOptionsGrouper utility** Add this before the `TripOptionsView` struct (around line 1570) in `TripCreationView.swift`: ```swift // MARK: - Trip Options Grouper enum TripOptionsGrouper { typealias GroupedOptions = (header: String, options: [ItineraryOption]) static func groupByCityCount(_ options: [ItineraryOption], ascending: Bool) -> [GroupedOptions] { let grouped = Dictionary(grouping: options) { option in Set(option.stops.map { $0.city }).count } let sorted = ascending ? grouped.sorted { $0.key < $1.key } : grouped.sorted { $0.key > $1.key } return sorted.map { count, opts in ("\(count) \(count == 1 ? "city" : "cities")", opts) } } static func groupByGameCount(_ options: [ItineraryOption], ascending: Bool) -> [GroupedOptions] { let grouped = Dictionary(grouping: options) { $0.totalGames } let sorted = ascending ? grouped.sorted { $0.key < $1.key } : grouped.sorted { $0.key > $1.key } return sorted.map { count, opts in ("\(count) \(count == 1 ? "game" : "games")", opts) } } static func groupByMileageRange(_ options: [ItineraryOption], ascending: Bool) -> [GroupedOptions] { let ranges: [(min: Int, max: Int, label: String)] = [ (0, 500, "0-500 mi"), (500, 1000, "500-1000 mi"), (1000, 1500, "1000-1500 mi"), (1500, 2000, "1500-2000 mi"), (2000, Int.max, "2000+ mi") ] var groupedDict: [String: [ItineraryOption]] = [:] for option in options { let miles = Int(option.totalDistanceMiles) for range in ranges { if miles >= range.min && miles < range.max { groupedDict[range.label, default: []].append(option) break } } } // Sort by range order let rangeOrder = ascending ? ranges : ranges.reversed() return rangeOrder.compactMap { range in guard let opts = groupedDict[range.label], !opts.isEmpty else { return nil } return (range.label, opts) } } } ``` **Step 4: Run tests to verify they pass** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TripOptionsGroupingTests test` Expected: All 5 tests PASS **Step 5: Commit** ```bash git add SportsTime/Features/Trip/Views/TripCreationView.swift SportsTimeTests/TripOptionsGroupingTests.swift git commit -m "$(cat <<'EOF' feat: add TripOptionsGrouper utility for grouped sorting Adds grouping functions for city count, game count, and mileage range with ascending/descending support. Used by TripOptionsView for sectioned display based on sort option. Co-Authored-By: Claude Opus 4.5 EOF )" ``` --- ## Task 4: Integrate Grouped Sections into TripOptionsView UI **Files:** - Modify: `SportsTime/Features/Trip/Views/TripCreationView.swift:1570-1839` (TripOptionsView) **Step 1: Add groupedOptions computed property to TripOptionsView** Add this computed property after `filteredAndSortedOptions` (around line 1635): ```swift private var groupedOptions: [TripOptionsGrouper.GroupedOptions] { switch sortOption { case .recommended, .bestEfficiency: // Flat list, no grouping return [("", filteredAndSortedOptions)] case .mostCities: return TripOptionsGrouper.groupByCityCount(filteredAndSortedOptions, ascending: false) case .mostGames: return TripOptionsGrouper.groupByGameCount(filteredAndSortedOptions, ascending: false) case .leastGames: return TripOptionsGrouper.groupByGameCount(filteredAndSortedOptions, ascending: true) case .mostMiles: return TripOptionsGrouper.groupByMileageRange(filteredAndSortedOptions, ascending: false) case .leastMiles: return TripOptionsGrouper.groupByMileageRange(filteredAndSortedOptions, ascending: true) } } ``` **Step 2: Replace the options list with sectioned display** In `TripOptionsView.body`, replace the options list section (the ForEach for `filteredAndSortedOptions` around lines 1668-1679) with: ```swift // Options list (grouped when applicable) if filteredAndSortedOptions.isEmpty { emptyFilterState .padding(.top, Theme.Spacing.xl) } else { ForEach(Array(groupedOptions.enumerated()), id: \.offset) { _, group in VStack(alignment: .leading, spacing: Theme.Spacing.sm) { // Section header (only if non-empty) if !group.header.isEmpty { HStack { Text(group.header) .font(.headline) .foregroundStyle(Theme.textPrimary(colorScheme)) Spacer() Text("\(group.options.count)") .font(.subheadline) .foregroundStyle(Theme.textMuted(colorScheme)) } .padding(.horizontal, Theme.Spacing.md) .padding(.top, Theme.Spacing.md) } // Options in this group ForEach(group.options) { option in TripOptionCard( option: option, games: games, onSelect: { selectedTrip = convertToTrip(option) showTripDetail = true } ) .padding(.horizontal, Theme.Spacing.md) } } } } ``` **Step 3: Build and verify UI** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build` Expected: Build succeeds. Run app, plan a trip, verify grouped sections appear when sorting by Most Cities, Most Games, etc. **Step 4: Commit** ```bash git add SportsTime/Features/Trip/Views/TripCreationView.swift git commit -m "$(cat <<'EOF' feat: add grouped sections to TripOptionsView - Add groupedOptions computed property using TripOptionsGrouper - Update UI to show section headers with counts when grouping - Recommended and Best Efficiency remain flat lists - Empty sections are automatically hidden Co-Authored-By: Claude Opus 4.5 EOF )" ``` --- ## Task 5: Run Full Test Suite and Final Verification **Files:** - None (verification only) **Step 1: Run full test suite** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test` Expected: All tests pass (including new PlanningTipsTests and TripOptionsGroupingTests) **Step 2: Verify features manually** 1. Launch app 2. Check Home tab → Planning Tips shows 3 random tips 3. Kill app, relaunch → verify tips changed 4. Plan a trip with multiple options 5. On TripOptionsView: - Select "Most Cities" → verify grouped by city count descending - Select "Least Games" → verify grouped by game count ascending - Select "Most Miles" → verify grouped by mileage ranges - Select "Recommended" → verify flat list (no headers) **Step 3: Final commit with all changes** ```bash git status # Verify all changes are committed ``` Expected: Working tree clean, all features implemented and tested. --- ## Summary | Task | Description | Tests | |------|-------------|-------| | 1 | Create PlanningTips data model with 100+ tips | 5 tests | | 2 | Wire PlanningTips into HomeView | Manual verification | | 3 | Add TripOptionsGrouper utility | 5 tests | | 4 | Integrate grouped sections into TripOptionsView UI | Manual verification | | 5 | Full test suite run and verification | All tests pass | **Total new tests:** 10 **Files created:** 2 (`PlanningTips.swift`, `PlanningTipsTests.swift`, `TripOptionsGroupingTests.swift`) **Files modified:** 2 (`HomeView.swift`, `TripCreationView.swift`)