diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index ac6b246..302ff17 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -42,6 +42,12 @@ Plans:
**Dependencies:** Phase 1 (semantic position model)
+**Plans:** 2 plans
+
+Plans:
+- [ ] 02-01-PLAN.md - Migrate XCTest constraint tests to Swift Testing
+- [ ] 02-02-PLAN.md - Add edge case tests and document constraint API
+
**Requirements:**
- CONS-01: Games cannot be moved (fixed by schedule)
- CONS-02: Travel segments constrained to valid day range
@@ -104,7 +110,7 @@ Plans:
| Phase | Status | Requirements | Completed |
|-------|--------|--------------|-----------|
| 1 - Semantic Position Model | ✓ Complete | 8 | 8 |
-| 2 - Constraint Validation | Not Started | 4 | 0 |
+| 2 - Constraint Validation | Planned | 4 | 0 |
| 3 - Visual Flattening | Not Started | 3 | 0 |
| 4 - Drag Interaction | Not Started | 8 | 0 |
diff --git a/.planning/phases/02-constraint-validation/02-01-PLAN.md b/.planning/phases/02-constraint-validation/02-01-PLAN.md
new file mode 100644
index 0000000..ce6f793
--- /dev/null
+++ b/.planning/phases/02-constraint-validation/02-01-PLAN.md
@@ -0,0 +1,189 @@
+---
+phase: 02-constraint-validation
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - SportsTimeTests/ItineraryConstraintsTests.swift
+autonomous: true
+user_setup: []
+
+must_haves:
+ truths:
+ - "All CONS-01 through CONS-04 requirements have corresponding passing tests"
+ - "Tests use Swift Testing framework (@Test, @Suite) matching Phase 1 patterns"
+ - "ItineraryConstraints API is fully tested with no coverage gaps"
+ artifacts:
+ - path: "SportsTimeTests/Domain/ItineraryConstraintsTests.swift"
+ provides: "Migrated constraint validation tests"
+ contains: "@Suite"
+ min_lines: 200
+ key_links:
+ - from: "SportsTimeTests/Domain/ItineraryConstraintsTests.swift"
+ to: "SportsTime/Core/Models/Domain/ItineraryConstraints.swift"
+ via: "import @testable SportsTime"
+ pattern: "@testable import SportsTime"
+---
+
+
+Migrate the 13 existing XCTest constraint tests to Swift Testing and move them to the Domain test folder.
+
+Purpose: Standardize test patterns across the project. Phase 1 established Swift Testing as the project standard; constraint tests should follow.
+Output: `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` with all tests passing using @Test/@Suite syntax.
+
+
+
+@~/.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/02-constraint-validation/02-RESEARCH.md
+
+# Pattern reference from Phase 1
+@SportsTimeTests/Domain/SortOrderProviderTests.swift
+@SportsTimeTests/Domain/SemanticPositionPersistenceTests.swift
+
+# Source test file to migrate
+@SportsTimeTests/ItineraryConstraintsTests.swift
+
+# Implementation being tested
+@SportsTime/Core/Models/Domain/ItineraryConstraints.swift
+
+
+
+
+
+ Task 1: Verify requirements coverage in existing tests
+ SportsTimeTests/ItineraryConstraintsTests.swift
+
+Read the existing 13 XCTest tests and map them to requirements:
+
+| Requirement | Test(s) | Coverage |
+|-------------|---------|----------|
+| CONS-01 (games cannot move) | test_gameItem_cannotBeMoved | Verify complete |
+| CONS-02 (travel day range) | test_travel_validDayRange_simpleCase, test_travel_cannotGoOutsideValidDayRange | Verify complete |
+| CONS-03 (travel sortOrder on game days) | test_travel_mustBeAfterDepartureGames, test_travel_mustBeBeforeArrivalGames, test_travel_mustBeAfterAllDepartureGamesOnSameDay, test_travel_mustBeBeforeAllArrivalGamesOnSameDay, test_travel_canBeAnywhereOnRestDays | Verify complete |
+| CONS-04 (custom no constraints) | test_customItem_canGoOnAnyDay, test_customItem_canGoBeforeOrAfterGames | Verify complete |
+
+Document any gaps found. If all requirements are covered, proceed to migration.
+
+ Requirements coverage table is complete with no gaps
+ All CONS-01 through CONS-04 requirements map to at least one existing test
+
+
+
+ Task 2: Migrate tests to Swift Testing
+ SportsTimeTests/Domain/ItineraryConstraintsTests.swift, SportsTimeTests/ItineraryConstraintsTests.swift
+
+1. Create new file at `SportsTimeTests/Domain/ItineraryConstraintsTests.swift`
+
+2. Convert XCTest syntax to Swift Testing:
+ - `final class ItineraryConstraintsTests: XCTestCase` -> `@Suite("ItineraryConstraints") struct ItineraryConstraintsTests`
+ - `func test_*()` -> `@Test("description") func *()` (preserve test names, add descriptive strings)
+ - `XCTAssertTrue(x)` -> `#expect(x == true)` or `#expect(x)`
+ - `XCTAssertFalse(x)` -> `#expect(x == false)` or `#expect(!x)`
+ - `XCTAssertEqual(a, b)` -> `#expect(a == b)`
+ - `XCTAssertNil(x)` -> `#expect(x == nil)`
+ - `import XCTest` -> `import Testing`
+
+3. Organize tests into logical groups using MARK comments:
+ - `// MARK: - Custom Item Tests (CONS-04)`
+ - `// MARK: - Travel Day Range Tests (CONS-02)`
+ - `// MARK: - Travel SortOrder Tests (CONS-03)`
+ - `// MARK: - Game Immutability Tests (CONS-01)`
+ - `// MARK: - Edge Cases`
+ - `// MARK: - Barrier Games`
+ - `// MARK: - Helpers`
+
+4. Preserve all helper methods (makeConstraints, makeGameItem, makeTravelItem, makeCustomItem)
+
+5. Delete the old file at `SportsTimeTests/ItineraryConstraintsTests.swift`
+
+Pattern reference - follow SortOrderProviderTests.swift style:
+```swift
+import Testing
+import Foundation
+@testable import SportsTime
+
+@Suite("ItineraryConstraints")
+struct ItineraryConstraintsTests {
+
+ // MARK: - Custom Item Tests (CONS-04)
+
+ @Test("custom: can go on any day")
+ func custom_canGoOnAnyDay() {
+ let constraints = makeConstraints(tripDays: 5, gameDays: [1, 5])
+ let customItem = makeCustomItem(day: 1, sortOrder: 50)
+
+ for day in 1...5 {
+ #expect(constraints.isValidPosition(for: customItem, day: day, sortOrder: 50))
+ }
+ }
+ // ...
+}
+```
+
+
+Run tests:
+```
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/ItineraryConstraintsTests test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)"
+```
+All 13 tests pass.
+
+ New file at Domain/ItineraryConstraintsTests.swift passes all 13 tests, old file deleted
+
+
+
+ Task 3: Run full test suite and commit
+ None (verification only)
+
+1. Run full test suite to verify no regressions:
+```
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)"
+```
+
+2. Commit the migration:
+```
+git add SportsTimeTests/Domain/ItineraryConstraintsTests.swift
+git rm SportsTimeTests/ItineraryConstraintsTests.swift
+git commit -m "test(02-01): migrate ItineraryConstraints tests to Swift Testing
+
+Migrate 13 XCTest tests to Swift Testing framework:
+- Move to Domain/ folder to match project structure
+- Convert XCTestCase to @Suite/@Test syntax
+- Update assertions to #expect macros
+- Verify all CONS-01 through CONS-04 requirements covered
+
+Co-Authored-By: Claude Opus 4.5 "
+```
+
+ Full test suite passes with no regressions
+ Migration committed, all tests pass including existing 34 Phase 1 tests
+
+
+
+
+
+After all tasks:
+1. `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` exists with @Suite/@Test syntax
+2. Old `SportsTimeTests/ItineraryConstraintsTests.swift` is deleted
+3. All 13 constraint tests pass
+4. Full test suite passes (no regressions)
+5. Tests organized by requirement (CONS-01 through CONS-04)
+
+
+
+- 13 tests migrated from XCTest to Swift Testing
+- Tests use @Test/@Suite syntax matching Phase 1 patterns
+- All CONS-01 through CONS-04 requirements have corresponding tests
+- Full test suite passes
+
+
+
diff --git a/.planning/phases/02-constraint-validation/02-02-PLAN.md b/.planning/phases/02-constraint-validation/02-02-PLAN.md
new file mode 100644
index 0000000..d1eef45
--- /dev/null
+++ b/.planning/phases/02-constraint-validation/02-02-PLAN.md
@@ -0,0 +1,449 @@
+---
+phase: 02-constraint-validation
+plan: 02
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - SportsTimeTests/Domain/ItineraryConstraintsTests.swift
+autonomous: true
+user_setup: []
+
+must_haves:
+ truths:
+ - "Edge cases are tested (empty trip, single-day trip, boundary sortOrders)"
+ - "Success criteria from roadmap are verifiable by tests"
+ - "Phase 4 has clear API documentation for drag-drop integration"
+ artifacts:
+ - path: "SportsTimeTests/Domain/ItineraryConstraintsTests.swift"
+ provides: "Complete constraint test suite with edge cases"
+ contains: "Edge Cases"
+ min_lines: 280
+ - path: ".planning/phases/02-constraint-validation/CONSTRAINT-API.md"
+ provides: "API documentation for Phase 4"
+ contains: "isValidPosition"
+ key_links:
+ - from: ".planning/phases/02-constraint-validation/CONSTRAINT-API.md"
+ to: "SportsTime/Core/Models/Domain/ItineraryConstraints.swift"
+ via: "documents public API"
+ pattern: "ItineraryConstraints"
+---
+
+
+Add edge case tests and create API documentation for Phase 4 integration.
+
+Purpose: Ensure constraint system handles boundary conditions and provide clear reference for drag-drop implementation.
+Output: Enhanced test suite with edge cases, API documentation for Phase 4.
+
+
+
+@~/.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/02-constraint-validation/02-RESEARCH.md
+
+# Implementation being tested
+@SportsTime/Core/Models/Domain/ItineraryConstraints.swift
+
+# Test file (will be enhanced)
+@SportsTimeTests/Domain/ItineraryConstraintsTests.swift
+
+
+
+
+
+ Task 1: Add edge case tests
+ SportsTimeTests/Domain/ItineraryConstraintsTests.swift
+
+Add the following edge case tests to the `// MARK: - Edge Cases` section:
+
+```swift
+// MARK: - Edge Cases
+
+@Test("edge: single-day trip accepts valid positions")
+func edge_singleDayTrip_acceptsValidPositions() {
+ let constraints = makeConstraints(tripDays: 1, gameDays: [])
+ let custom = makeCustomItem(day: 1, sortOrder: 50)
+
+ #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 50))
+ #expect(!constraints.isValidPosition(for: custom, day: 0, sortOrder: 50))
+ #expect(!constraints.isValidPosition(for: custom, day: 2, sortOrder: 50))
+}
+
+@Test("edge: day 0 is always invalid")
+func edge_day0_isAlwaysInvalid() {
+ let constraints = makeConstraints(tripDays: 5, gameDays: [])
+ let custom = makeCustomItem(day: 1, sortOrder: 50)
+
+ #expect(!constraints.isValidPosition(for: custom, day: 0, sortOrder: 50))
+}
+
+@Test("edge: day beyond trip is invalid")
+func edge_dayBeyondTrip_isInvalid() {
+ let constraints = makeConstraints(tripDays: 3, gameDays: [])
+ let custom = makeCustomItem(day: 1, sortOrder: 50)
+
+ #expect(!constraints.isValidPosition(for: custom, day: 4, sortOrder: 50))
+ #expect(!constraints.isValidPosition(for: custom, day: 100, sortOrder: 50))
+}
+
+@Test("edge: travel at exact game sortOrder boundary is invalid")
+func edge_travelAtExactGameSortOrder_isInvalid() {
+ // Game at sortOrder 100
+ let constraints = makeConstraints(
+ tripDays: 3,
+ games: [makeGameItem(city: "Chicago", day: 1, sortOrder: 100)]
+ )
+ let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 100)
+
+ // Exactly AT game sortOrder should be invalid (must be AFTER)
+ #expect(!constraints.isValidPosition(for: travel, day: 1, sortOrder: 100))
+
+ // Just after should be valid
+ #expect(constraints.isValidPosition(for: travel, day: 1, sortOrder: 100.001))
+}
+
+@Test("edge: travel with no games in either city has full range")
+func edge_travelNoGamesInEitherCity_hasFullRange() {
+ let constraints = makeConstraints(tripDays: 5, games: [])
+ let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50)
+
+ // Valid on any day
+ for day in 1...5 {
+ #expect(constraints.isValidPosition(for: travel, day: day, sortOrder: 50))
+ }
+
+ // Full range
+ #expect(constraints.validDayRange(for: travel) == 1...5)
+}
+
+@Test("edge: negative sortOrder is valid for custom items")
+func edge_negativeSortOrder_validForCustomItems() {
+ let constraints = makeConstraints(tripDays: 3, gameDays: [])
+ let custom = makeCustomItem(day: 2, sortOrder: -100)
+
+ #expect(constraints.isValidPosition(for: custom, day: 2, sortOrder: -100))
+}
+
+@Test("edge: very large sortOrder is valid for custom items")
+func edge_veryLargeSortOrder_validForCustomItems() {
+ let constraints = makeConstraints(tripDays: 3, gameDays: [])
+ let custom = makeCustomItem(day: 2, sortOrder: 10000)
+
+ #expect(constraints.isValidPosition(for: custom, day: 2, sortOrder: 10000))
+}
+```
+
+Also add a test verifying the roadmap success criteria are testable:
+
+```swift
+// MARK: - Success Criteria Verification
+
+@Test("success: game row shows no drag interaction (game not draggable)")
+func success_gameNotDraggable() {
+ // Games return false for ANY position, making them non-draggable
+ let game = makeGameItem(city: "Chicago", day: 2, sortOrder: 100)
+ let constraints = makeConstraints(tripDays: 5, games: [game])
+
+ // Same position
+ #expect(!constraints.isValidPosition(for: game, day: 2, sortOrder: 100))
+ // Different day
+ #expect(!constraints.isValidPosition(for: game, day: 1, sortOrder: 100))
+ // Different sortOrder
+ #expect(!constraints.isValidPosition(for: game, day: 2, sortOrder: 50))
+}
+
+@Test("success: custom note can be placed anywhere")
+func success_customNotePlacedAnywhere() {
+ // Custom items can be placed before, between, or after games on any day
+ let constraints = makeConstraints(
+ tripDays: 3,
+ games: [
+ makeGameItem(city: "Chicago", day: 1, sortOrder: 100),
+ makeGameItem(city: "Chicago", day: 1, sortOrder: 200),
+ makeGameItem(city: "Detroit", day: 3, sortOrder: 100)
+ ]
+ )
+ let custom = makeCustomItem(day: 1, sortOrder: 50)
+
+ // Before games on day 1
+ #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 50))
+ // Between games on day 1
+ #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 150))
+ // After games on day 1
+ #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 250))
+ // On rest day (day 2)
+ #expect(constraints.isValidPosition(for: custom, day: 2, sortOrder: 50))
+ // On day 3 with different city games
+ #expect(constraints.isValidPosition(for: custom, day: 3, sortOrder: 50))
+}
+
+@Test("success: invalid position returns false (rejection)")
+func success_invalidPositionReturnsRejection() {
+ // Travel segment cannot be placed before departure game
+ let constraints = makeConstraints(
+ tripDays: 3,
+ games: [makeGameItem(city: "Chicago", day: 2, sortOrder: 100)]
+ )
+ let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50)
+
+ // Day 1 is before Chicago game on Day 2, so invalid
+ #expect(!constraints.isValidPosition(for: travel, day: 1, sortOrder: 50))
+}
+```
+
+
+Run updated tests:
+```
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/ItineraryConstraintsTests test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)"
+```
+All tests pass (original 13 + 10 new edge cases = 23 total).
+
+ 10 additional edge case tests added and passing
+
+
+
+ Task 2: Create API documentation for Phase 4
+ .planning/phases/02-constraint-validation/CONSTRAINT-API.md
+
+Create API documentation that Phase 4 can reference for drag-drop integration:
+
+```markdown
+# ItineraryConstraints API
+
+**Location:** `SportsTime/Core/Models/Domain/ItineraryConstraints.swift`
+**Verified by:** 23 tests in `SportsTimeTests/Domain/ItineraryConstraintsTests.swift`
+
+## Overview
+
+`ItineraryConstraints` validates item positions during drag-drop operations. It enforces:
+
+- **Games cannot move** (CONS-01)
+- **Travel segments have day range limits** (CONS-02)
+- **Travel segments must respect game sortOrder on same day** (CONS-03)
+- **Custom items have no constraints** (CONS-04)
+
+## Construction
+
+```swift
+let constraints = ItineraryConstraints(
+ tripDayCount: days.count,
+ items: allItineraryItems // All items including games
+)
+```
+
+**Parameters:**
+- `tripDayCount`: Total days in trip (1-indexed, so a 5-day trip has days 1-5)
+- `items`: All itinerary items (games, travel, custom). Games are used to calculate constraints for travel items.
+
+## Public API
+
+### `isValidPosition(for:day:sortOrder:) -> Bool`
+
+Check if a specific position is valid for an item.
+
+```swift
+func isValidPosition(for item: ItineraryItem, day: Int, sortOrder: Double) -> Bool
+```
+
+**Usage during drag:**
+```swift
+// On each drag position update
+let dropPosition = calculateDropPosition(at: touchLocation)
+let isValid = constraints.isValidPosition(
+ for: draggedItem,
+ day: dropPosition.day,
+ sortOrder: dropPosition.sortOrder
+)
+
+if isValid {
+ showValidDropIndicator()
+} else {
+ showInvalidDropIndicator()
+}
+```
+
+**Returns:**
+- `true`: Position is valid, allow drop
+- `false`: Position is invalid, reject drop (snap back)
+
+**Rules by item type:**
+| Item Type | Day Constraint | SortOrder Constraint |
+|-----------|----------------|----------------------|
+| `.game` | Always `false` | Always `false` |
+| `.travel` | Within valid day range | After departure games, before arrival games |
+| `.custom` | Any day 1...tripDayCount | Any sortOrder |
+
+### `validDayRange(for:) -> ClosedRange?`
+
+Get the valid day range for a travel item (for visual feedback).
+
+```swift
+func validDayRange(for item: ItineraryItem) -> ClosedRange?
+```
+
+**Usage at drag start:**
+```swift
+// When drag begins, precompute valid range
+guard case .travel = draggedItem.kind,
+ let validRange = constraints.validDayRange(for: draggedItem) else {
+ // Not a travel item or impossible constraints
+ return
+}
+
+// Use range to dim invalid days
+for day in 1...tripDayCount {
+ if !validRange.contains(day) {
+ dimDay(day)
+ }
+}
+```
+
+**Returns:**
+- `ClosedRange`: Valid day range (e.g., `2...4`)
+- `nil`: Constraints are impossible (e.g., departure game after arrival game)
+
+### `barrierGames(for:) -> [ItineraryItem]`
+
+Get games that constrain a travel item (for visual highlighting).
+
+```swift
+func barrierGames(for item: ItineraryItem) -> [ItineraryItem]
+```
+
+**Usage for visual feedback:**
+```swift
+// Highlight barrier games during drag
+let barriers = constraints.barrierGames(for: travelItem)
+for barrier in barriers {
+ highlightAsBarrier(barrier) // e.g., gold border
+}
+```
+
+**Returns:**
+- Array of game items: Last departure city game + first arrival city game
+- Empty array: Not a travel item or no constraining games
+
+## Integration Points
+
+### ItineraryTableViewController (existing)
+
+```swift
+// In reloadData()
+self.constraints = ItineraryConstraints(tripDayCount: tripDayCount, items: itineraryItems)
+
+// In drag handling
+if constraints.isValidPosition(for: draggedItem, day: targetDay, sortOrder: targetSortOrder) {
+ // Allow drop
+} else {
+ // Reject drop, snap back
+}
+```
+
+### Phase 4 Implementation Notes
+
+1. **Drag Start:**
+ - Check `item.isReorderable` (games return `false`)
+ - Call `validDayRange(for:)` to precompute valid days
+ - Call `barrierGames(for:)` to identify visual barriers
+
+2. **Drag Move:**
+ - Calculate target (day, sortOrder) from touch position
+ - Call `isValidPosition(for:day:sortOrder:)` for real-time feedback
+ - Update insertion line (valid) or red indicator (invalid)
+
+3. **Drag End:**
+ - Final `isValidPosition(for:day:sortOrder:)` check
+ - Valid: Update item's day/sortOrder, animate settle
+ - Invalid: Animate snap back, haptic feedback
+
+## Test Coverage
+
+| Requirement | Tests | Verified |
+|-------------|-------|----------|
+| CONS-01 (games cannot move) | 2 | Yes |
+| CONS-02 (travel day range) | 5 | Yes |
+| CONS-03 (travel sortOrder) | 5 | Yes |
+| CONS-04 (custom flexibility) | 4 | Yes |
+| Edge cases | 7 | Yes |
+| **Total** | **23** | **100%** |
+
+---
+*API documented: Phase 02*
+*Ready for: Phase 04 (Drag Interaction)*
+```
+
+ File exists and contains all three public methods with usage examples
+ CONSTRAINT-API.md created with complete API documentation
+
+
+
+ Task 3: Run full test suite and commit
+ None (verification only)
+
+1. Run full test suite to verify no regressions:
+```
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)"
+```
+
+2. Commit the edge case tests:
+```
+git add SportsTimeTests/Domain/ItineraryConstraintsTests.swift
+git commit -m "test(02-02): add edge case tests for constraint validation
+
+Add 10 edge case tests:
+- Single-day trip boundaries
+- Day 0 and beyond-trip validation
+- Exact sortOrder boundary behavior
+- Travel with no games in cities
+- Negative and large sortOrders
+- Success criteria verification tests
+
+Total: 23 constraint tests
+
+Co-Authored-By: Claude Opus 4.5 "
+```
+
+3. Commit the API documentation:
+```
+git add .planning/phases/02-constraint-validation/CONSTRAINT-API.md
+git commit -m "docs(02-02): document ItineraryConstraints API for Phase 4
+
+Document public API for drag-drop integration:
+- isValidPosition() for position validation
+- validDayRange() for precomputing valid days
+- barrierGames() for visual highlighting
+- Integration patterns for ItineraryTableViewController
+
+Co-Authored-By: Claude Opus 4.5 "
+```
+
+ Full test suite passes with no regressions
+ Edge case tests and API documentation committed
+
+
+
+
+
+After all tasks:
+1. 23 total constraint tests pass (13 migrated + 10 edge cases)
+2. Full test suite passes (no regressions)
+3. CONSTRAINT-API.md exists with complete documentation
+4. All commits follow project conventions
+
+
+
+- Edge cases tested: single-day, day boundaries, sortOrder boundaries, no-games scenarios
+- Roadmap success criteria are verifiable by tests
+- API documentation complete for Phase 4 integration
+- All tests pass
+
+
+