Files
SportstimeAPI/.planning/research/FEATURES.md
Trey t 6f09c0abcb docs: complete project research for itinerary editor
Files:
- STACK.md - UITableView drag-drop APIs, SwiftUI bridging patterns
- FEATURES.md - Table stakes UX (lift, insertion line, haptics), polish features
- ARCHITECTURE.md - 5-layer semantic positioning architecture
- PITFALLS.md - Critical pitfalls (row vs semantic, travel as structural)
- SUMMARY.md - Executive synthesis with roadmap implications

Key findings:
- Stack: UITableView + UIHostingConfiguration (existing pattern validated)
- Architecture: Semantic (day, sortOrder) model, not row indices
- Critical pitfall: Row indices are ephemeral; semantic positions are truth

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 13:16:07 -06:00

244 lines
12 KiB
Markdown

# Features Research: Drag-Drop Editor UX
**Domain:** Drag-and-drop itinerary editor for iOS sports travel app
**Researched:** 2026-01-18
**Confidence:** HIGH (multiple authoritative sources cross-verified)
## Executive Summary
Polished drag-drop requires deliberate visual feedback at every state transition. The difference between "feels broken" and "feels delightful" comes down to: lift animation, predictable insertion indicators, smooth reshuffling, and magnetic snap-to-place. Your constraints (fixed day headers, fixed games, movable travel/custom items) add complexity but are achievable with proper drop zone logic.
---
## Table Stakes
Features users expect. Missing any of these makes the editor feel broken.
| Feature | Why Expected | Complexity | Implementation Notes |
|---------|--------------|------------|---------------------|
| **Lift animation on grab** | Users expect physical metaphor - picking up an object | Low | Elevation (shadow), scale 1.02-1.05x, slight z-offset |
| **Ghost/placeholder at origin** | Shows where item came from, reduces anxiety | Low | Semi-transparent copy or outlined placeholder in original position |
| **Insertion indicator line** | Must show exactly where item will drop | Medium | Horizontal line with small terminal bleeds, appears between items |
| **Items move out of the way** | Preview of final state while dragging | Medium | ~100ms animation, triggered when dragged item center overlaps edge |
| **Magnetic snap on drop** | Satisfying completion, confirms action worked | Low | 100ms ease-out animation to final position |
| **Clear invalid drop feedback** | Don't leave user guessing why drop failed | Low | Item animates back to origin if dropped in invalid zone |
| **Touch hold delay (300-500ms)** | Distinguish tap from drag intent | Low | iOS standard; prevents accidental drags |
| **Haptic on grab** | Tactile confirmation drag started | Low | UIImpactFeedbackGenerator.light on pickup |
| **Haptic on drop** | Tactile confirmation action completed | Low | UIImpactFeedbackGenerator.medium on successful drop |
| **Scroll when dragging to edge** | Lists longer than viewport need auto-scroll | Medium | Scroll speed increases closer to edge, ~40px threshold |
### Insertion Indicator Details
The insertion line is critical. Best practices:
- Appears **between** items (in the gap), not on top
- Has small terminal bleeds (~4px) extending past item edges
- Triggered when center of dragged item crosses edge of potential neighbor
- Color should contrast clearly (system accent or distinct color)
### Animation Timing
| Event | Duration | Easing |
|-------|----------|--------|
| Lift (pickup) | 150ms | ease-out |
| Items shuffling | 100ms | ease-out |
| Snap to place (drop) | 100ms | ease-out |
| Return to origin (cancel) | 200ms | ease-in-out |
---
## Nice-to-Have
Polish features that delight but aren't expected.
| Feature | Value | Complexity | Notes |
|---------|-------|------------|-------|
| **Slight tilt on drag (2-3 degrees)** | Trello's signature polish; makes interaction feel playful | Low | Rotate3D effect, matches brand personality |
| **Progressive drop zone highlighting** | Visual intensifies as item approaches valid zone | Medium | Background color change, border enhancement |
| **Multi-item drag with count badge** | Power users moving multiple items at once | High | Not needed for v1; itinerary items are usually moved one at a time |
| **Keyboard reordering (a11y)** | Up/Down arrows via rotor actions | Medium | Important for accessibility; add accessibilityActions |
| **Undo after drop** | Recover from mistakes | Medium | Toast with "Undo" button, ~5 second timeout |
| **Drag handle icon** | Visual affordance for draggability | Low | 6-dot grip icon (Notion-style) or horizontal lines |
| **Cancel drag with escape/shake** | Quick abort | Low | Shake-to-cancel on iOS; return to origin |
| **Drop zone "ready" state** | Zone visually activates before item enters | Low | Subtle background shift when drag starts |
### Tilt Animation (Trello-style)
The 2-3 degree tilt on dragged items is considered "gold standard" polish:
- Adds personality without being distracting
- Reinforces physical metaphor (picking up a card)
- Should match your app's design language (may be too playful for some apps)
---
## Overkill
Skip these - high complexity, low value for an itinerary editor.
| Feature | Why Skip | What to Do Instead |
|---------|----------|-------------------|
| **Drag between sections/screens** | Your items live within days; cross-day moves are rare | Allow within same list only, or use "Move to..." action menu |
| **Nested drag-drop** | Games within days is hierarchy enough | Keep flat list per day section |
| **Free-form canvas positioning** | Not applicable to linear itinerary | Stick to list reordering |
| **Real-time collaborative drag** | Massive sync complexity | Single-user editing |
| **Drag-to-resize** | Items don't have variable size | Fixed item heights |
| **Custom drag preview images** | Native preview is sufficient | Use default lifted appearance |
| **Physics-based spring animations** | Overkill for list reordering | Simple ease-out is fine |
---
## Interactions to Support
Specific drag scenarios for your itinerary context.
### Scenario 1: Move Custom Item Within Same Day
**User intent:** Reorder "Dinner at Lou Malnati's" from after to before the Cubs game
**Expected behavior:**
1. Long-press on custom item (300ms) - haptic feedback
2. Item lifts (shadow + scale), ghost remains at origin
3. Drag within day section - insertion line appears between valid positions
4. Games and travel segments shuffle with 100ms animation
5. Drop - item snaps into place, haptic confirms
**Constraints:**
- Custom item can move anywhere within the day
- Cannot move before/after day header
- Cannot replace or overlay a game (games are fixed)
### Scenario 2: Move Custom Item to Different Day
**User intent:** Move hotel check-in from Day 2 to Day 1
**Expected behavior:**
1. Long-press and lift
2. Drag toward Day 1 section
3. Auto-scroll if Day 1 is off-screen
4. Insertion line appears at valid positions in Day 1
5. Day 2 collapses to show item removed; Day 1 expands
6. Drop - item now in Day 1
**Constraints:**
- Can cross day boundaries
- Still cannot land on games
### Scenario 3: Move Travel Segment (Constrained)
**User intent:** Move "Drive: Chicago to Milwaukee" earlier in the day
**Expected behavior:**
1. Long-press on travel segment
2. Item lifts (possibly with different visual treatment since it's constrained)
3. Insertion line only appears at **valid** positions (before/after games it connects)
4. Invalid positions show no insertion line (or dimmed indicator)
5. If dropped at invalid position, item animates back to origin
**Constraints:**
- Travel segments connect stadiums/locations
- Can only move within logical route order
- Must validate position before showing insertion indicator
### Scenario 4: Attempt to Move Fixed Item (Game)
**User intent:** User tries to drag a game (not allowed)
**Expected behavior:**
1. Long-press on game item
2. **No lift animation** - item doesn't respond as draggable
3. Optionally: subtle shake or tooltip "Games cannot be reordered"
4. User understands this item is fixed
**Visual differentiation:**
- Fixed items should NOT have drag handles
- Could have different visual treatment (no grip icon, different background)
### Scenario 5: Drag to Invalid Zone
**User intent:** User drags custom item but releases over a game
**Expected behavior:**
1. Item is being dragged
2. Hovers over game - no insertion line appears (invalid)
3. User releases
4. Item animates back to origin (~200ms)
5. Optional: brief error state or haptic warning
---
## Visual States Summary
| Element State | Visual Treatment |
|--------------|------------------|
| **Resting (draggable)** | Normal appearance, optional drag handle icon on hover/focus |
| **Resting (fixed)** | Normal, but NO drag handle; visually distinct |
| **Lifted/grabbed** | Elevated (shadow), slight scale up (1.02-1.05), optional tilt |
| **Ghost at origin** | Semi-transparent (30-50% opacity) or outlined placeholder |
| **Insertion line** | Accent-colored horizontal line, ~2px height, bleeds past edges |
| **Invalid drop zone** | No insertion line; item over zone dims or shows warning |
| **Drop zone ready** | Subtle background color shift when any drag starts |
| **Dropped/success** | Snaps to place, haptic feedback, ghost disappears |
| **Cancelled/error** | Returns to origin with animation, optional warning haptic |
---
## Accessibility Requirements
| Requirement | Implementation | Priority |
|-------------|----------------|----------|
| **VoiceOver reordering** | accessibilityActions with "Move Up" / "Move Down" | High |
| **Rotor integration** | Actions appear in VoiceOver rotor | High |
| **Focus management** | Focus follows moved item after reorder | Medium |
| **Live region announcements** | Announce position change ("Item moved to position 3") | Medium |
| **Fallback buttons** | Optional up/down arrows as visual alternative | Low (nice to have) |
SwiftUI example for accessibility:
```swift
.accessibilityAction(named: "Move Up") { moveItemUp(item) }
.accessibilityAction(named: "Move Down") { moveItemDown(item) }
```
---
## Mobile-Specific Considerations
| Concern | Solution |
|---------|----------|
| **Fat finger problem** | Minimum 44x44pt touch targets; drag handles at least 44pt wide |
| **Scroll vs. drag conflict** | Long-press delay (300-500ms) distinguishes intent |
| **Viewport limitations** | Auto-scroll at edges (40px threshold), speed increases near edge |
| **One-handed use** | Consider "Move to..." button as alternative to long-distance drags |
| **Accidental drops** | Generous drop zones; magnetic snap; undo option |
---
## Anti-Patterns to Avoid
| Anti-Pattern | Why Bad | Do Instead |
|--------------|---------|------------|
| **Edge-to-edge shuffle trigger** | Feels "twitchy", items move unexpectedly | Use center-overlap-edge trigger |
| **Instant reshuffle (no animation)** | Disorienting, hard to track what moved | 100ms animated transitions |
| **No ghost/placeholder** | User loses context of original position | Always show origin indicator |
| **Drag handle too small** | Frustrating on touch | Minimum 44pt, ideally larger |
| **Remove item during drag** | Anxiety - "where did it go?" | Keep ghost visible at origin |
| **Scroll too fast at edges** | Overshoots, loses control | Gradual speed increase |
| **No invalid feedback** | User thinks interaction is broken | Clear visual/haptic for invalid drops |
---
## Sources
**High Confidence (verified with multiple authoritative sources):**
- [Smart Interface Design Patterns - Drag and Drop UX](https://smart-interface-design-patterns.com/articles/drag-and-drop-ux/)
- [Atlassian Pragmatic Drag and Drop Design Guidelines](https://atlassian.design/components/pragmatic-drag-and-drop/design-guidelines/)
- [Pencil & Paper - Drag & Drop UX Design Best Practices](https://www.pencilandpaper.io/articles/ux-pattern-drag-and-drop)
- [Nielsen Norman Group - Drag and Drop: How to Design for Ease of Use](https://www.nngroup.com/articles/drag-drop/)
**Medium Confidence (single authoritative source):**
- [LogRocket - Designing Drag and Drop UIs](https://blog.logrocket.com/ux-design/drag-and-drop-ui-examples/)
- [Darin Senneff - Designing a Reorderable List Component](https://www.darins.page/articles/designing-a-reorderable-list-component)
- [Apple Human Interface Guidelines - Drag and Drop](https://developer.apple.com/design/human-interface-guidelines/drag-and-drop)
**Low Confidence (community patterns):**
- Various SwiftUI implementation guides (verify APIs against current documentation)
- Trello UX patterns referenced in multiple articles (de facto standard)