Appearance
Use Case #4: Отмена и Повтор действий (Undo / Redo / Ctrl+Z)
Исходное состояние: Пользователь строит маршрут и совершает действие: ставит случайную точку в стороне, неудачно тянет трек, удаляет важный чекпоинт или меняет профиль с «Шоссе» на «MTB» (из-за чего трек перестраивается по корягам). Действие: Пользователь нажимает кнопку ↩️ (Undo) в UI или использует шорткат Ctrl+Z / Cmd+Z.
1. Интент (Зачем это юзеру на самом деле?)
Два принципиально разных мотива:
- Режим "Упс, ошибка" (Паника/Спасение): Случайный мисклик, дрогнула рука. Нужно мгновенно вернуть всё как было.
- Режим "Исследование / A/B тест": Юзер думает: "А что если пустить маршрут вооон через ту гору?" Перетягивает туда трек, видит, что набор высоты подскочил на +1000м. Жмет Undo, затем Redo, чтобы снова оценить цифры, и выбирает лучший вариант. Здесь Undo — это полноценный инструмент аналитики.
2. Идеальная логика системы (State Management)
- Изоляция мутаций от вьюпорта: В стек истории (History Stack) должны записываться только смысловые изменения маршрута. Смещение карты мышкой (Pan) или масштабирование (Zoom) НИКОГДА не должны попадать в стек.
- Zero-Latency (Мгновенность из кэша): При нажатии Undo система не должна заново отправлять запрос к серверам роутинга. Состояние трека (геометрия + метрики) должно сохраняться локально (Snapshots) и доставаться из памяти мгновенно для комфортного A/B тестирования.
3. Визуальный отклик и интерфейс (UX/UI)
- Умная камера (Smart Viewport) — Критический момент! Если измененный участок находится вне текущего экрана (юзер сдвинул карту после ошибки), камера обязана сделать плавный перелет (Pan & Zoom), чтобы место изменения оказалось в центре. Пользователь должен увидеть результат отмены!
- Синхронизация метрик: Цифры дистанции и график высот должны мгновенно прыгнуть к старым значениям. Идеально — добавить легкую CSS-анимацию подсветки изменения.
- Индикация кнопок: Кнопки ↩️ и ↪️ всегда на видном месте. Неактивные состояния визуально серые (Disabled).
4. Антипаттерны (Как делать НЕ надо)
- ❌ Сброс стека (Амнезия): Очистка Undo при "автосохранении" или сворачивании панелей. Стек должен жить локально до закрытия вкладки.
- ❌ Запись микро-движений (Drag & Drop): Движение мыши не должно создавать 10 разных шагов в Undo. Весь процесс
MouseDown -> Drag -> MouseUp— это строго один атомарный шаг истории. - ❌ Конфликт фокуса ввода: Нажатие Ctrl+Z стирает букву в строке поиска, а не отменяет маршрут (или наоборот). Хоткеи карты должны корректно отключаться, если фокус активно стоит в текстовом инпуте.
5. Edge Cases (Граничные случаи)
- Отмена в процессе ожидания (Лоадер): Юзер бросил точку далеко, появился лоадер. Он тут же жмет Ctrl+Z.
- Решение: Система мгновенно прерывает (
AbortController) текущий API-запрос к движку роутинга и откатывает стейт.
- Решение: Система мгновенно прерывает (
- Глобальное удаление: Случайное нажатие "Очистить маршрут" (Clear All).
- Решение: Полное удаление обязано попадать в стек Undo. Кнопка ↩️ становится активной для полного спасения работы.
- Временной парадокс (Перезапись будущего): Юзер сделал 5 шагов. Нажал Undo до шага "2" и сделал новое действие.
- Решение: Будущая ветка Redo (шаги 3, 4, 5) безвозвратно удаляется.
Эпики и Задачи (Epics & Tasks)
Декомпозиция Use Case #4 для разработки.
Epic 1: Управление состоянием (Zustand History Stack)
- Task 1.1: Архитектура Undo/Redo. Интегрировать middleware (например,
zundo) или написать кастомную логику поверхplannerStore. Стек должен хранить полныеsnapshotsмассива Waypoints, метрик и геометрии трека, чтобы возврат происходил за 0 мс без API вызовов. - Task 1.2: Изоляция UI состояния. Убедиться, что стейт карты (
center,zoom,pitch) вынесен в отдельный стор или исключен из отслеживания Undo. Стек истории реагирует только на мутации роутинга. - Task 1.3: Очистка Redo при новой мутации (Time Paradox). При совершении любого нового смыслового действия (добавление точки) стек
future(Redo) должен полностью очищаться. - Task 1.4: Поддержка глобальной очистки. Действие "Clear Route" должно просто пушить пустой snapshot в стек истории, а не сбрасывать сам стек.
Epic 2: Обработка событий и Хоткеи
- Task 2.1: Атомарность Drag & Drop. При работе с "резинкой" или перемещении точек запись в стек Undo должна происходить только один раз — на событие
onDragEnd(или после получения ответа от сервера). - Task 2.2: Глобальный перехватчик (Keyboard Listener). Добавить обработчик для Ctrl+Z / Cmd+Z и Ctrl+Shift+Z / Cmd+Shift+Z. Написать проверку: если
document.activeElementявляетсяinputилиtextarea, событие отмены маршрута игнорируется (браузер отрабатывает свой нативный текстовый Undo). - Task 2.3: Отмена API запросов (AbortController). Доработать API адаптер роутинга: при вызове Undo/Redo все pending запросы к BRouter/OSRM немедленно прерываются через
AbortSignal, чтобы не затереть возвращенный из кэша стейт опоздавшим ответом.
Epic 3: Визуальный отклик (Smart Viewport & Animations)
- Task 3.1: Умная камера при Undo (Smart Viewport).
- При срабатывании Undo система вычисляет
Bounding Boxразницы между старым и новым состоянием трека/точек. - Если эта область не помещается в текущий
Map viewport, вызывается методfitBoundsилиflyTo, чтобы принудительно показать пользователю место изменения.
- При срабатывании Undo система вычисляет
- Task 3.2: Анимация изменения метрик. Добавить легкую CSS-вспышку или перелистывание цифр в компонентах
DistanceWidgetиElevationWidgetпри срабатывании Undo/Redo. - Task 3.3: Состояния UI кнопок. Связать
disabledсостояние кнопок ↩️ и ↪️ с длинами массивовpastиfutureв сторе.