docs(03): create phase 3 visual flattening plans
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>
This commit is contained in:
180
.planning/phases/03-visual-flattening/03-01-PLAN.md
Normal file
180
.planning/phases/03-visual-flattening/03-01-PLAN.md
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.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
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create ItineraryFlattener utility</name>
|
||||
<files>SportsTime/Core/Models/Domain/ItineraryFlattener.swift</files>
|
||||
<action>
|
||||
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
|
||||
</action>
|
||||
<verify>
|
||||
Build succeeds: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -5`
|
||||
</verify>
|
||||
<done>
|
||||
`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)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Refactor reloadData() to use ItineraryFlattener</name>
|
||||
<files>SportsTime/Features/Trip/Views/ItineraryTableViewController.swift</files>
|
||||
<action>
|
||||
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
|
||||
</action>
|
||||
<verify>
|
||||
Build succeeds: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -5`
|
||||
</verify>
|
||||
<done>
|
||||
`reloadData()` method:
|
||||
- Uses `ItineraryFlattener.flatten()` instead of manual loop
|
||||
- No longer contains beforeGames/afterGames bucket logic
|
||||
- Still updates travelValidRanges, allItineraryItems, tripDayCount, constraints
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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()`
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-visual-flattening/03-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user