Phase 03: Visual Flattening - 2 plans in 2 waves - Plan 01: Create ItineraryFlattener utility, refactor reloadData() - Plan 02: Add determinism tests for flattening behavior - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-visual-flattening | 01 | execute | 1 |
|
true |
|
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.
<execution_context>
@/.claude/get-shit-done/workflows/execute-plan.md
@/.claude/get-shit-done/templates/summary.md
</execution_context>
The function signature:
enum ItineraryFlattener {
static func flatten(
days: [ItineraryDayData],
itineraryItems: [ItineraryItem],
gamesByDay: [Int: [RichGame]]
) -> [ItineraryRowItem]
}
Implementation logic:
- For each day in order:
a. Add
.dayHeaderrow (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.sortOrderdirectly - Travel items: look up sortOrder from
itineraryItemsarray by matching city names c. Sort collected items by sortOrder (ascending) d. Append sorted items to result
- Games: use
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
travelBeforeproperty Build succeeds:xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -5ItineraryFlattener.swiftexists withflatten()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)
Current flow to replace:
// 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:
// 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:
- Build
gamesByDaydictionary:[Int: [RichGame]] - Call
ItineraryFlattener.flatten()to getflatItems - Reload table view
Preserve:
self.travelValidRanges = travelValidRangesself.allItineraryItems = itineraryItemsself.tripDayCount = days.countself.constraints = ItineraryConstraints(...)rebuildtableView.reloadData()call at end
Remove:
- The manual flattening loop with beforeGames/afterGames buckets
- Any remaining
travelBeforereferences 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 -5reloadData()method: - Uses
ItineraryFlattener.flatten()instead of manual loop - No longer contains beforeGames/afterGames bucket logic
- Still updates travelValidRanges, allItineraryItems, tripDayCount, constraints
<success_criteria>
- 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) </success_criteria>