From 98941a3b4de85ae7e351c4933d5fc01015293162 Mon Sep 17 00:00:00 2001 From: Trey t Date: Sun, 18 Jan 2026 13:08:13 -0600 Subject: [PATCH] docs: initialize project Interactive drag-and-drop itinerary editor with semantic positioning --- .planning/PROJECT.md | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .planning/PROJECT.md diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md new file mode 100644 index 0000000..c4f2adb --- /dev/null +++ b/.planning/PROJECT.md @@ -0,0 +1,75 @@ +# 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*