# Flexible Itinerary Ordering Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Enable fully flexible ordering of itinerary items - custom items can go anywhere, travel is reorderable within day constraints, games have sortOrder but are immovable.

**Architecture:** Extend existing `TravelDayOverride` model with `sortOrder` field. Update flattening logic to sort all orderable items (games, travel, custom) together by sortOrder within each day. Games get auto-assigned sortOrder (100, 101...) but no drag handle.

**Tech Stack:** Swift, SwiftUI, UIKit (UITableViewController), CloudKit

---

## Task 1: Update TravelDayOverride Domain Model

**Files:**
- Modify: `SportsTime/Core/Models/Domain/TravelDayOverride.swift`

**Step 1: Add sortOrder field to TravelDayOverride**

```swift
struct TravelDayOverride: Identifiable, Codable, Hashable {
    let id: UUID
    let tripId: UUID
    let travelAnchorId: String
    var displayDay: Int
    var sortOrder: Double  // NEW: Position within day (allows interleaving with custom items)
    let createdAt: Date
    var modifiedAt: Date

    init(
        id: UUID = UUID(),
        tripId: UUID,
        travelAnchorId: String,
        displayDay: Int,
        sortOrder: Double = 50.0,  // Default before games (games start at 100)
        createdAt: Date = Date(),
        modifiedAt: Date = Date()
    ) {
        self.id = id
        self.tripId = tripId
        self.travelAnchorId = travelAnchorId
        self.displayDay = displayDay
        self.sortOrder = sortOrder
        self.createdAt = createdAt
        self.modifiedAt = modifiedAt
    }
}
```

**Step 2: Build to verify compilation**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20`

Expected: Build succeeds (existing code still works due to default parameter)

**Step 3: Commit**

```bash
git add SportsTime/Core/Models/Domain/TravelDayOverride.swift
git commit -m "feat(model): add sortOrder to TravelDayOverride"
```

---

## Task 2: Update CloudKit Model for TravelDayOverride

**Files:**
- Modify: `SportsTime/Core/Models/CloudKit/CKModels.swift` (CKTravelDayOverride section)

**Step 1: Add sortOrder field to CKTravelDayOverride**

Find `CKTravelDayOverride` struct and add:

```swift
struct CKTravelDayOverride {
    static let overrideIdKey = "overrideId"
    static let tripIdKey = "tripId"
    static let travelAnchorIdKey = "travelAnchorId"
    static let displayDayKey = "displayDay"
    static let sortOrderKey = "sortOrder"  // NEW
    static let createdAtKey = "createdAt"
    static let modifiedAtKey = "modifiedAt"

    let record: CKRecord

    init(record: CKRecord) {
        self.record = record
    }

    init(override: TravelDayOverride) {
        let record = CKRecord(
            recordType: CKRecordType.travelDayOverride,
            recordID: CKRecord.ID(recordName: override.id.uuidString)
        )
        record[CKTravelDayOverride.overrideIdKey] = override.id.uuidString
        record[CKTravelDayOverride.tripIdKey] = override.tripId.uuidString
        record[CKTravelDayOverride.travelAnchorIdKey] = override.travelAnchorId
        record[CKTravelDayOverride.displayDayKey] = override.displayDay
        record[CKTravelDayOverride.sortOrderKey] = override.sortOrder  // NEW
        record[CKTravelDayOverride.createdAtKey] = override.createdAt
        record[CKTravelDayOverride.modifiedAtKey] = override.modifiedAt
        self.record = record
    }

    func toOverride() -> TravelDayOverride? {
        guard let overrideIdString = record[CKTravelDayOverride.overrideIdKey] as? String,
              let overrideId = UUID(uuidString: overrideIdString),
              let tripIdString = record[CKTravelDayOverride.tripIdKey] as? String,
              let tripId = UUID(uuidString: tripIdString),
              let travelAnchorId = record[CKTravelDayOverride.travelAnchorIdKey] as? String,
              let displayDay = record[CKTravelDayOverride.displayDayKey] as? Int,
              let createdAt = record[CKTravelDayOverride.createdAtKey] as? Date,
              let modifiedAt = record[CKTravelDayOverride.modifiedAtKey] as? Date
        else { return nil }

        // Migration: default sortOrder for existing records without it
        let sortOrder = record[CKTravelDayOverride.sortOrderKey] as? Double ?? 50.0

        return TravelDayOverride(
            id: overrideId,
            tripId: tripId,
            travelAnchorId: travelAnchorId,
            displayDay: displayDay,
            sortOrder: sortOrder,
            createdAt: createdAt,
            modifiedAt: modifiedAt
        )
    }
}
```

**Step 2: Build to verify compilation**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20`

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Core/Models/CloudKit/CKModels.swift
git commit -m "feat(cloudkit): add sortOrder to CKTravelDayOverride"
```

---

## Task 3: Update TravelOverrideService

**Files:**
- Modify: `SportsTime/Core/Services/TravelOverrideService.swift`

**Step 1: Update saveOverride to persist sortOrder**

In `saveOverride(_:)`, add the sortOrder field when updating existing record:

```swift
if let existing = existingOverride {
    let recordID = CKRecord.ID(recordName: existing.id.uuidString)
    do {
        let existingRecord = try await publicDatabase.record(for: recordID)
        existingRecord[CKTravelDayOverride.displayDayKey] = override.displayDay
        existingRecord[CKTravelDayOverride.sortOrderKey] = override.sortOrder  // NEW
        existingRecord[CKTravelDayOverride.modifiedAtKey] = now
        record = existingRecord
        // ...
    }
}
```

**Step 2: Update fetchOverridesAsDictionary return type**

Change from `[String: Int]` to return full `TravelDayOverride` objects:

```swift
/// Fetch all travel overrides for a trip, keyed by travelAnchorId
/// If duplicate travelAnchorIds exist (legacy data), takes the most recently modified one
func fetchOverridesAsDictionary(forTripId tripId: UUID) async throws -> [String: TravelDayOverride] {
    let overrides = try await fetchOverrides(forTripId: tripId)
    let sorted = overrides.sorted { $0.modifiedAt > $1.modifiedAt }
    return Dictionary(sorted.map { ($0.travelAnchorId, $0) }) { first, _ in first }
}
```

**Step 3: Build to verify compilation**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20`

Expected: Build fails (TripDetailView uses old return type) - this is expected

**Step 4: Commit**

```bash
git add SportsTime/Core/Services/TravelOverrideService.swift
git commit -m "feat(service): update TravelOverrideService for sortOrder"
```

---

## Task 4: Update ItineraryTableViewWrapper Interface

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewWrapper.swift`

**Step 1: Change travelDayOverrides type**

Update the property and init:

```swift
let travelDayOverrides: [String: TravelDayOverride]  // Changed from [String: Int]
```

Update init parameter:

```swift
init(
    trip: Trip,
    games: [RichGame],
    customItems: [CustomItineraryItem],
    travelDayOverrides: [String: TravelDayOverride],  // Changed
    // ...
)
```

**Step 2: Update buildItineraryData to use sortOrder**

In `buildItineraryData()`, update the override lookup:

```swift
// Use override if valid, otherwise use default
if let override = travelDayOverrides[travelId], validRange.contains(override.displayDay) {
    travelByDay[override.displayDay] = (segment, override.sortOrder)
} else {
    let clampedDefault = max(validRange.lowerBound, min(defaultDay, validRange.upperBound))
    travelByDay[clampedDefault] = (segment, 50.0)  // Default sortOrder before games
}
```

**Step 3: Update ItineraryDayData to store travel sortOrder**

Change `travelBefore` to include sortOrder:

```swift
struct ItineraryDayData: Identifiable {
    let id: Int
    let dayNumber: Int
    let date: Date
    let games: [RichGame]
    var items: [ItineraryRowItem]
    var travelBefore: (segment: TravelSegment, sortOrder: Double)?  // Changed
    // ...
}
```

**Step 4: Build (expect failures - controller needs update)**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | grep -E "error:|warning:" | head -20`

**Step 5: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewWrapper.swift
git commit -m "feat(wrapper): update interface for travel sortOrder"
```

---

## Task 5: Update ItineraryRowItem to Include sortOrder

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewController.swift`

**Step 1: Add sortOrder to ItineraryRowItem enum**

Update cases to include sortOrder where needed:

```swift
enum ItineraryRowItem: Identifiable, Equatable {
    case dayHeader(dayNumber: Int, date: Date)
    case games([RichGame], dayNumber: Int, sortOrder: Double)  // Added sortOrder
    case travel(TravelSegment, dayNumber: Int, sortOrder: Double)  // Added sortOrder
    case customItem(CustomItineraryItem)  // Already has sortOrder in the model

    /// Get sortOrder for sorting within a day (nil for headers)
    var sortOrder: Double? {
        switch self {
        case .dayHeader:
            return nil
        case .games(_, _, let order):
            return order
        case .travel(_, _, let order):
            return order
        case .customItem(let item):
            return item.sortOrder
        }
    }

    // Update id and isReorderable as needed...
}
```

**Step 2: Update id property**

```swift
var id: String {
    switch self {
    case .dayHeader(let dayNumber, _):
        return "day:\(dayNumber)"
    case .games(_, let dayNumber, _):
        return "games:\(dayNumber)"
    case .travel(let segment, _, _):
        return "travel:\(segment.fromLocation.name.lowercased())->\(segment.toLocation.name.lowercased())"
    case .customItem(let item):
        return "item:\(item.id.uuidString)"
    }
}
```

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewController.swift
git commit -m "feat(controller): add sortOrder to ItineraryRowItem"
```

---

## Task 6: Update Flattening Logic to Sort All Items Together

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewController.swift`

**Step 1: Rewrite reloadData flattening**

Replace the flattening loop in `reloadData(days:travelValidRanges:)`:

```swift
func reloadData(days: [ItineraryDayData], travelValidRanges: [String: ClosedRange<Int>]) {
    self.travelValidRanges = travelValidRanges

    flatItems = []

    for day in days {
        // 1. Day header (fixed, always first)
        flatItems.append(.dayHeader(dayNumber: day.dayNumber, date: day.date))

        // 2. Collect all orderable items for this day
        var orderableItems: [ItineraryRowItem] = []

        // Games get sortOrder starting at 100
        for (index, game) in day.games.enumerated() {
            let gameSortOrder = 100.0 + Double(index)
            orderableItems.append(.games([game], dayNumber: day.dayNumber, sortOrder: gameSortOrder))
        }

        // Travel (if arriving this day)
        if let (travel, sortOrder) = day.travelBefore {
            orderableItems.append(.travel(travel, dayNumber: day.dayNumber, sortOrder: sortOrder))
        }

        // Custom items
        for item in day.items {
            if case .customItem = item {
                orderableItems.append(item)
            }
        }

        // 3. Sort all orderable items by sortOrder
        orderableItems.sort { ($0.sortOrder ?? 0) < ($1.sortOrder ?? 0) }

        // 4. Append sorted items
        flatItems.append(contentsOf: orderableItems)
    }

    tableView.reloadData()
}
```

**Step 2: Build to verify**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20`

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewController.swift
git commit -m "feat(controller): sort all orderable items together by sortOrder"
```

---

## Task 7: Update Drag Constraints for Custom Items

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewController.swift`

**Step 1: Update targetIndexPathForMoveFromRowAt for custom items**

Remove the constraints that prevent custom items from going before games/travel. In the `.customItem` case:

```swift
case .customItem:
    // Custom items can go anywhere except:
    // 1. Before position 0
    // 2. Directly ON a day header (redirect to after header)

    // Don't drop ON a day header - go after it instead
    if proposedRow < flatItems.count, case .dayHeader = flatItems[proposedRow] {
        return IndexPath(row: proposedRow + 1, section: 0)
    }

    // Allow dropping anywhere else (before games, before travel, etc.)
    return IndexPath(row: proposedRow, section: 0)
```

**Step 2: Update calculateSortOrder to consider all orderable items**

Update `calculateSortOrder(at:)` to scan for games and travel, not just custom items:

```swift
private func calculateSortOrder(at row: Int) -> Double {
    var prevSortOrder: Double?
    var nextSortOrder: Double?

    // SCAN BACKWARDS to find previous orderable item in this day
    for i in stride(from: row - 1, through: 0, by: -1) {
        switch flatItems[i] {
        case .dayHeader:
            // Hit day boundary - no previous item in this day
            break
        case .games(_, _, let order):
            prevSortOrder = order
        case .travel(_, _, let order):
            prevSortOrder = order
        case .customItem(let item):
            prevSortOrder = item.sortOrder
        }
        if prevSortOrder != nil { break }
        if case .dayHeader = flatItems[i] { break }
    }

    // SCAN FORWARDS to find next orderable item in this day
    for i in row..<flatItems.count {
        switch flatItems[i] {
        case .dayHeader:
            // Hit next day boundary
            break
        case .games(_, _, let order):
            nextSortOrder = order
        case .travel(_, _, let order):
            nextSortOrder = order
        case .customItem(let item):
            nextSortOrder = item.sortOrder
        }
        if nextSortOrder != nil { break }
        if case .dayHeader = flatItems[i] { break }
    }

    // CALCULATE sortOrder based on what we found
    switch (prevSortOrder, nextSortOrder) {
    case (nil, nil):
        return 1.0
    case (let prev?, nil):
        return prev + 1.0
    case (nil, let next?):
        return next / 2.0
    case (let prev?, let next?):
        return (prev + next) / 2.0
    }
}
```

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewController.swift
git commit -m "feat(controller): allow custom items anywhere, update sortOrder calc"
```

---

## Task 8: Update Travel Move Callback

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewController.swift`

**Step 1: Update onTravelMoved callback signature**

Change callback to include sortOrder:

```swift
var onTravelMoved: ((String, Int, Double) -> Void)?  // travelId, newDay, newSortOrder
```

**Step 2: Update moveRowAt to pass sortOrder**

In `tableView(_:moveRowAt:to:)`:

```swift
case .travel(let segment, _):
    let newDay = dayForTravelAt(row: destinationIndexPath.row)
    let sortOrder = calculateSortOrder(at: destinationIndexPath.row)
    let travelId = "travel:\(segment.fromLocation.name.lowercased())->\(segment.toLocation.name.lowercased())"
    onTravelMoved?(travelId, newDay, sortOrder)
```

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewController.swift
git commit -m "feat(controller): include sortOrder in travel move callback"
```

---

## Task 9: Update ItineraryTableViewWrapper Callback

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewWrapper.swift`

**Step 1: Update onTravelMoved callback type**

```swift
var onTravelMoved: ((String, Int, Double) -> Void)?  // travelId, newDay, newSortOrder
```

**Step 2: Update makeUIViewController**

```swift
controller.onTravelMoved = onTravelMoved
```

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewWrapper.swift
git commit -m "feat(wrapper): update travel move callback for sortOrder"
```

---

## Task 10: Update TripDetailView State and Handlers

**Files:**
- Modify: `SportsTime/Features/Trip/Views/TripDetailView.swift`

**Step 1: Update state type**

```swift
@State private var travelDayOverrides: [String: TravelDayOverride] = [:]
```

**Step 2: Update onTravelMoved handler**

```swift
onTravelMoved: { travelId, newDay, newSortOrder in
    Task { @MainActor in
        // Create or update the override
        let override = TravelDayOverride(
            id: travelDayOverrides[travelId]?.id ?? UUID(),
            tripId: trip.id,
            travelAnchorId: travelId,
            displayDay: newDay,
            sortOrder: newSortOrder,
            createdAt: travelDayOverrides[travelId]?.createdAt ?? Date(),
            modifiedAt: Date()
        )

        withAnimation {
            travelDayOverrides[travelId] = override
        }

        await saveTravelDayOverride(override)
    }
}
```

**Step 3: Update saveTravelDayOverride function**

Change signature and implementation:

```swift
private func saveTravelDayOverride(_ override: TravelDayOverride) async {
    do {
        _ = try await TravelOverrideService.shared.saveOverride(override)
        print("✅ [TravelOverride] Saved: \(override.travelAnchorId) -> day \(override.displayDay), sortOrder \(override.sortOrder)")
    } catch {
        print("❌ [TravelOverride] Failed to save: \(error)")
    }
}
```

**Step 4: Update loadCustomItems to use new return type**

```swift
let overrides = try await TravelOverrideService.shared.fetchOverridesAsDictionary(forTripId: trip.id)
print("✅ [TravelOverrides] Loaded \(overrides.count) travel day overrides")
travelDayOverrides = overrides
```

**Step 5: Build and fix any remaining compilation errors**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -30`

**Step 6: Commit**

```bash
git add SportsTime/Features/Trip/Views/TripDetailView.swift
git commit -m "feat(tripdetail): update state and handlers for travel sortOrder"
```

---

## Task 11: Update Cell Configuration for Games

**Files:**
- Modify: `SportsTime/Features/Trip/Views/ItineraryTableViewController.swift`

**Step 1: Update cellForRowAt for games case**

```swift
case .games(let games, _, _):
    let cell = tableView.dequeueReusableCell(withIdentifier: gamesCellId, for: indexPath)
    configureGamesCell(cell, games: games)
    return cell
```

**Step 2: Update travel case**

```swift
case .travel(let segment, _, _):
    let cell = tableView.dequeueReusableCell(withIdentifier: travelCellId, for: indexPath)
    configureTravelCell(cell, segment: segment)
    return cell
```

**Step 3: Build to verify**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20`

**Step 4: Commit**

```bash
git add SportsTime/Features/Trip/Views/ItineraryTableViewController.swift
git commit -m "fix(controller): update cell configuration for new enum cases"
```

---

## Task 12: Full Build and Manual Test

**Step 1: Full clean build**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' clean build 2>&1 | tail -30`

Expected: BUILD SUCCEEDED

**Step 2: Run tests**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | tail -50`

Expected: All tests pass

**Step 3: Manual verification checklist**

- [ ] Open app, navigate to a trip with games and travel
- [ ] Verify games appear with no drag handle
- [ ] Verify custom items have drag handle
- [ ] Verify travel has drag handle
- [ ] Drag custom item BEFORE a game - should work
- [ ] Drag custom item AFTER a game - should work
- [ ] Drag travel to different position within same day - should work
- [ ] Drag travel to different valid day - should work
- [ ] Drag travel to invalid day - should snap to nearest valid

**Step 4: Commit**

```bash
git add -A
git commit -m "feat: complete flexible itinerary ordering implementation"
```

---

## Task 13: Write Regression Tests

**Files:**
- Create: `SportsTimeTests/Features/ItineraryOrderingTests.swift`

**Step 1: Create test file with ordering tests**

```swift
import XCTest
@testable import SportsTime

final class ItineraryOrderingTests: XCTestCase {

    // MARK: - sortOrder Calculation Tests

    func test_calculateSortOrder_betweenTwoItems_returnsMidpoint() {
        // Given items at sortOrder 1.0 and 3.0
        // When calculating sortOrder for position between them
        // Then should return 2.0
    }

    func test_calculateSortOrder_beforeFirstItem_returnsHalf() {
        // Given first item at sortOrder 2.0
        // When calculating sortOrder for position before it
        // Then should return 1.0 (half of 2.0)
    }

    func test_calculateSortOrder_afterLastItem_returnsLastPlusOne() {
        // Given last item at sortOrder 5.0
        // When calculating sortOrder for position after it
        // Then should return 6.0
    }

    func test_calculateSortOrder_emptyDay_returnsOne() {
        // Given day with no orderable items
        // When calculating sortOrder
        // Then should return 1.0
    }

    // MARK: - Travel Constraint Tests

    func test_travelConstraint_cannotMoveBefore DepartureGame() {
        // Given travel from City A to City B
        // And City A has game on Day 2
        // When attempting to move travel to Day 1
        // Then travel should snap to Day 3 (minimum valid)
    }

    func test_travelConstraint_canMoveToArrivalGameDay() {
        // Given travel from City A to City B
        // And City B has game on Day 5
        // When moving travel to Day 5
        // Then should be allowed (arrive morning of game)
    }

    // MARK: - Custom Item Flexibility Tests

    func test_customItem_canBePositionedBeforeGame() {
        // Given a day with a game at sortOrder 100
        // When custom item dropped before game
        // Then custom item should get sortOrder < 100
    }

    func test_customItem_canBePositionedBetweenTravelAndGame() {
        // Given travel at sortOrder 50, game at sortOrder 100
        // When custom item dropped between them
        // Then custom item should get sortOrder ~75
    }
}
```

**Step 2: Run tests**

Run: `cd /Users/treyt/Desktop/code/SportsTime && xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/ItineraryOrderingTests test 2>&1 | tail -30`

**Step 3: Commit**

```bash
git add SportsTimeTests/Features/ItineraryOrderingTests.swift
git commit -m "test: add regression tests for flexible itinerary ordering"
```

---

## Summary

This plan implements flexible itinerary ordering in 13 tasks:

1. **Tasks 1-3:** Update data models (domain, CloudKit, service) to support sortOrder on travel
2. **Tasks 4-6:** Update wrapper and controller interfaces for new data structure
3. **Tasks 7-9:** Update drag constraints and callbacks
4. **Task 10:** Update TripDetailView state management
5. **Tasks 11-12:** Fix compilation, full build, manual test
6. **Task 13:** Write regression tests

Key changes:
- `TravelDayOverride` gains `sortOrder: Double`
- Games get auto-assigned sortOrder (100, 101, 102...)
- All orderable items (games, travel, custom) sort together within each day
- Custom items can now go anywhere (before/after games)
- Travel remains constrained to valid day range but can be positioned within a day
