76 lines
3.7 KiB
Markdown
76 lines
3.7 KiB
Markdown
# Itinerary Editor
|
|
|
|
## What This Is
|
|
|
|
An interactive drag-and-drop itinerary editor for the SportsTime iOS app. Users can rearrange travel segments and custom items within their trip itinerary while respecting game schedules and city-based travel constraints. Built as a UITableView bridged into SwiftUI to enable precise drag-and-drop with insertion line feedback.
|
|
|
|
## Core Value
|
|
|
|
Drag-and-drop that operates on semantic positions (day + sortOrder), not row indices — so user intent is preserved across data reloads.
|
|
|
|
## Requirements
|
|
|
|
### Validated
|
|
|
|
- ✓ Trip itineraries display with day headers and games — existing (`TripDetailView`)
|
|
- ✓ Conflict detection for same-day games in different cities — existing
|
|
- ✓ SwiftUI + SwiftData architecture — existing
|
|
|
|
### Active
|
|
|
|
- [ ] Semantic position model using `(day: Int, sortOrder: Double)` for all movable items
|
|
- [ ] Custom items can be placed anywhere within any day (including between games)
|
|
- [ ] Travel segments respect city constraints (after from-city games, before to-city games)
|
|
- [ ] Travel segments respect day range constraints (within valid travel window)
|
|
- [ ] Invalid drops are rejected with snap-back behavior
|
|
- [ ] Insertion lines show precise drop targets during drag
|
|
- [ ] External drops (from outside the table) work with same semantic rules
|
|
- [ ] Position persists correctly across data reloads
|
|
- [ ] No visual jumpiness or dead zones during drag operations
|
|
|
|
### Out of Scope
|
|
|
|
- Reordering games — games are fixed by schedule
|
|
- Reordering day headers — structural, one per day
|
|
- Zone-based drop highlighting — using insertion lines instead
|
|
- Multi-day travel segments — travel belongs to exactly one day
|
|
|
|
## Context
|
|
|
|
**Existing codebase:** SportsTime iOS app with Clean MVVM architecture. `TripDetailView` already displays itineraries with conflict detection. The new editor replaces the display-only view with an interactive one.
|
|
|
|
**Technical environment:**
|
|
- iOS 26+, Swift 6 concurrency
|
|
- SwiftUI drives data, UITableView handles drag-and-drop
|
|
- SwiftData for persistence
|
|
- Frequent reloads from data changes; visual-only state is not acceptable
|
|
|
|
**What went wrong in previous attempts:**
|
|
- Row-based snapping instead of semantic (day, sortOrder)
|
|
- Treating travel as structural ("travelBefore") instead of positional
|
|
- Losing sortOrder for travel during flattening
|
|
- Hard-coded flatten order (header → games → customs) that ignored sortOrder
|
|
- Drag logic and reload logic fighting each other
|
|
|
|
## Constraints
|
|
|
|
- **Architecture**: SwiftUI wrapper must drive data; UIKit table handles drag/drop mechanics
|
|
- **Persistence**: All positions must be semantic (day, sortOrder) — no ephemeral visual state
|
|
- **Reload tolerance**: Reloads must not undo valid user actions; position must survive reload
|
|
- **iOS version**: iOS 26+ (per existing app target)
|
|
|
|
## Key Decisions
|
|
|
|
| Decision | Rationale | Outcome |
|
|
|----------|-----------|---------|
|
|
| UITableView over SwiftUI List | SwiftUI drag-and-drop lacks insertion line precision and external drop support | — Pending |
|
|
| (day, sortOrder) position model | Row indices break on reload; semantic position is reload-stable | — Pending |
|
|
| Insertion lines (not zones) | User wants precise feedback on exact drop location | — Pending |
|
|
| Custom items interleave with games | Maximum flexibility for user — can add notes between games | — Pending |
|
|
| Travel position-constrained within day | After from-city games, before to-city games on same day | — Pending |
|
|
| Invalid drops rejected (snap back) | Cleaner than auto-clamping; user knows exactly what happened | — Pending |
|
|
| Items always belong to a day | No liminal "between days" state; visual gap is end of previous day | — Pending |
|
|
|
|
---
|
|
*Last updated: 2026-01-18 after initialization*
|