--- phase: 03-visual-flattening plan: 01 type: execute wave: 1 depends_on: [] files_modified: - SportsTime/Core/Models/Domain/ItineraryFlattener.swift - SportsTime/Features/Trip/Views/ItineraryTableViewController.swift autonomous: true must_haves: truths: - "Items with lower sortOrder appear before items with higher sortOrder within each day" - "Items with sortOrder < 0 appear before games" - "Items with sortOrder >= 0 appear after or between games" - "Day headers always appear first in their day's section" artifacts: - path: "SportsTime/Core/Models/Domain/ItineraryFlattener.swift" provides: "Pure flatten function" exports: ["flatten(days:itineraryItems:gamesByDay:)"] - path: "SportsTime/Features/Trip/Views/ItineraryTableViewController.swift" provides: "Refactored reloadData() using ItineraryFlattener" contains: "ItineraryFlattener.flatten" key_links: - from: "ItineraryTableViewController.reloadData()" to: "ItineraryFlattener.flatten()" via: "function call" pattern: "ItineraryFlattener\\.flatten" --- Create a pure ItineraryFlattener utility that transforms hierarchical day data into display rows sorted by sortOrder. Purpose: Replace the bucket-based flattening (beforeGames/afterGames arrays) with pure sortOrder sorting, ensuring deterministic display order. Output: New `ItineraryFlattener.swift` file and refactored `reloadData()` method using it. @~/.claude/get-shit-done/workflows/execute-plan.md @~/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-visual-flattening/03-RESEARCH.md @.planning/phases/03-visual-flattening/03-CONTEXT.md @SportsTime/Core/Models/Domain/SortOrderProvider.swift @SportsTime/Features/Trip/Views/ItineraryTableViewController.swift @SportsTime/Features/Trip/Views/ItineraryTableViewWrapper.swift Task 1: Create ItineraryFlattener utility SportsTime/Core/Models/Domain/ItineraryFlattener.swift Create a new `ItineraryFlattener.swift` file with a pure flatten function. The function signature: ```swift enum ItineraryFlattener { static func flatten( days: [ItineraryDayData], itineraryItems: [ItineraryItem], gamesByDay: [Int: [RichGame]] ) -> [ItineraryRowItem] } ``` Implementation logic: 1. For each day in order: a. Add `.dayHeader` row (always first for each day) b. Collect all positionable items with their sortOrder: - Games: use `SortOrderProvider.initialSortOrder(forGameTime:)` for first game's time - Custom items: use `item.sortOrder` directly - Travel items: look up sortOrder from `itineraryItems` array by matching city names c. Sort collected items by sortOrder (ascending) d. Append sorted items to result Key decisions (from CONTEXT.md): - sortOrder < 0 appears BEFORE games (games have sortOrder 100-1540) - sortOrder >= 0 appears AFTER/BETWEEN games - Day header is NOT a positioned item - always first Do NOT: - Split into beforeGames/afterGames buckets - Use hardcoded order assumptions - Special-case travel with `travelBefore` property Build succeeds: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -5` `ItineraryFlattener.swift` exists with `flatten()` function that: - Sorts all items within a day by sortOrder - Day headers appear first in each day - Function is pure (no side effects, no instance state) Task 2: Refactor reloadData() to use ItineraryFlattener SportsTime/Features/Trip/Views/ItineraryTableViewController.swift Refactor the `reloadData()` method (currently lines 484-545) to use the new `ItineraryFlattener.flatten()` function. Current flow to replace: ```swift // OLD: Bucket-based approach var beforeGames: [ItineraryRowItem] = [] var afterGames: [ItineraryRowItem] = [] for row in day.items { if sortOrder < 0 { beforeGames.append(row) } else { afterGames.append(row) } } flatItems.append(contentsOf: beforeGames) flatItems.append(.games(...)) flatItems.append(contentsOf: afterGames) ``` New flow: ```swift // NEW: Pure sortOrder-based approach let gamesByDay = Dictionary(grouping: days.flatMap { ($0.games, $0.dayNumber) }) { ... } flatItems = ItineraryFlattener.flatten( days: days, itineraryItems: allItineraryItems, gamesByDay: gamesByDay ) ``` The refactored method should: 1. Build `gamesByDay` dictionary: `[Int: [RichGame]]` 2. Call `ItineraryFlattener.flatten()` to get `flatItems` 3. Reload table view Preserve: - `self.travelValidRanges = travelValidRanges` - `self.allItineraryItems = itineraryItems` - `self.tripDayCount = days.count` - `self.constraints = ItineraryConstraints(...)` rebuild - `tableView.reloadData()` call at end Remove: - The manual flattening loop with beforeGames/afterGames buckets - Any remaining `travelBefore` references in the flatten logic Build succeeds: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -5` `reloadData()` method: - Uses `ItineraryFlattener.flatten()` instead of manual loop - No longer contains beforeGames/afterGames bucket logic - Still updates travelValidRanges, allItineraryItems, tripDayCount, constraints 1. Build succeeds with no errors 2. `ItineraryFlattener.swift` exists in `SportsTime/Core/Models/Domain/` 3. `reloadData()` in `ItineraryTableViewController.swift` calls `ItineraryFlattener.flatten()` 4. No `beforeGames` or `afterGames` variables remain in `reloadData()` - FLAT-01: Items sorted by sortOrder within each day (implemented in ItineraryFlattener) - FLAT-03: sortOrder < 0 before games, >= 0 after (handled by natural sort + game sortOrder range 100-1540) After completion, create `.planning/phases/03-visual-flattening/03-01-SUMMARY.md`