# #522 — маршруты и сохранение цели Дизайн модели, не реализованный native router. Прототип: [index.html](index.html). Сценарии выбираются снаружи телефона; системные результаты — в отдельной внешней панели. Все сущности — fixtures. ## Главный маршрут A | Шаг | Действие / переход | Данные и ограничения | |---|---|---| | Системный launch | Готова оболочка → welcome либо известная цель | Нет рекламного таймера. До первого кадра только статичный брендовый launch | | Первый содержательный экран | «Выбрать город и смотреть» → city; «Войти» → общий chooser | Игры, площадки, люди; гостевой просмотр заметен | | City | Поиск или строка города → games | Только явный выбор; сохраняется cityId/timezone каталога. Нет server profile write | | Games | Карточка → game; вкладки → venues/players | Публичные поля и доступы сервера. Без guest identity и demo login | | Game | «Подать заявку» → auth у гостя / reread у авторизованного | IntendedAction = join(gameId), сохраняются разрешённые фильтры и destination | | Auth | VK / Яндекс / email → system browser → проверенный handoff | Native exchange/refresh пока отсутствуют. Методы определяются доступностью сервера | | Auth success | Явное «Вернуться к условиям» → reread game/participation | Не выполняет заявку. Приватность нового профиля объяснена, старые настройки сохранены | | Confirm | Свежие время, цена, местность, вместимость → «Подтвердить заявку» | Версия и права проверяются заново. При изменении условий нужна новая кнопка подтверждения | | Result | pending / confirmed / actual error → «Мои игры» | Серверный статус. Pending не гарантирует место. При неизвестном исходе сначала GET/reconcile | После результата можно предложить только выбранные события push и необязательное дополнение профиля. Пропуск не мешает продолжению. ## Другие точки входа | Сценарий | Начало → следующий экран | Возврат / исключения | |---|---|---| | B, первый запуск | Каталог без города → city внутри контекста → результат | Никаких придуманных игр до ответа каталога | | C, первый запуск | Игра / площадка / люди → city → выбранный каталог | Выбор цели не создаёт аккаунт | | Повторный гость | Последний явный город/фильтр → games | Приветствие не повторяется; нет city — только city | | Повторный авторизованный | Мои игры / сохранённый последний корневой экран | Сессия валидируется; cached private data не показывать до восстановления identity | | Истёкшая сессия | Публичная игра остаётся → «Войти снова» | Для private notification сначала auth; отмена возвращает публичную игру, не приватный статус | | Cold game link | Bootstrap → public game по ID | Без welcome/city gate; город игры не меняет город каталога. Missing/private → neutral unavailable | | Warm game link | Push route в текущий stack → game | Back возвращает в предыдущий контекст | | Public profile link | Bootstrap → публичный profile по ID | Те же проверки доступа; написать → account-bound request | | Push tap | Минимальный event/reference → identity check → reread destination | Данные payload не доверенный статус. Чужой аккаунт/отозванный доступ → neutral unavailable | | Перезапуск посреди auth | Safe intended destination → recovery → новая auth transaction | Незавершённые коды не replay; истёкший verifier удаляется. Отмена сохраняет публичную цель | | Обновление | Существующее состояние → обычный маршрут | Не повторять welcome. При несовместимой API/runtime версии — отдельный upgrade gate | | Установка после store | Обычный first launch | Deferred link не реализован и не обещается. Пользователь может заново открыть исходную HTTPS-ссылку | | Выход | Подтвердить → revoke + detach token + clear private caches → guest games | Город поиска можно сохранить; закрываются приватные маршруты и стираются личные черновики | | Смена аккаунта | Выход → chooser → новый account namespace | Старая заявка/переписка не переносится; device binding создаётся заново | ## Вход и ошибки | Состояние | Показ / действие | Что сохраняем | |---|---|---| | Новый / существующий аккаунт | Общий chooser; email объясняет создание, если способ ещё не существует | Единая account identity; не связывать по совпавшему email | | Email | Дизайн страницы в системном браузере → код → success | Адрес только в живой transaction, не публичное имя | | Wrong code | Inline error с label и alert; retry по вводу | Адрес, цель; не менять auth state | | Expired code | Заблокирован verify; «Получить новый» | Серверные TTL/resend policy, новый challenge вместо replay | | OAuth cancel | Chooser с нейтральным сообщением; «Вернуться без входа» | Город, фильтр, выбранная игра | | Provider failure | Ошибка без пустого экрана; повтор или другой доступный способ | Не auto-link и не обход identity checks | | Нет связи при чтении | Error + explicit retry | Выбранный город; offline mutation disabled | | Потерян ответ mutation | «Проверить статус», затем reconciliation | Тот же operation/idempotency context; новая заявка не отправляется вслепую | | Capacity/conditions changed | Свежий confirm либо «Мест нет» | Не повторять исходную mutation с устаревшей версией | ## Сообщение и создание игры Гость может открыть публичный профиль и составить запрос на общение. Вход сохраняет черновик текущего процесса и возвращает к форме; отправка отдельной кнопкой. Contact requests не заменяются безусловным открытием диалога. Черновики сообщений по умолчанию только в памяти; переживание завершения процесса требует отдельного безопасного хранения и согласованного срока, не plaintext persistence. На restart гарантируется публичная цель, не сохранение приватного текста. «Создать игру» собирает контекстные поля организатора. В макете показаны имя организатора → вход → возврат к черновику → следующий шаг. Полная форма создания намеренно вне onboarding-среза: venue/activity, hostDisplayName 1–160, нормализованный публичный контакт, timezone/time/deadline/capacity/levelRule/settlement переиспользуют существующий контракт. Черновик не публикуется автоматически. Не смешивать hostDisplayName с обязательным публичным именем профиля. ## Разрешения City → «Рядом со мной» → location context → OS prompt. Denied/restricted/services-off → manual city. Approximate → подтверждение предполагаемого города; без точного расстояния. Ни один ответ ОС не включает Radar. Profile → Radar → отдельное product consent → включение с серверной проверкой. Profile → Photo → system item picker; Camera permission только по «Сделать снимок». После участия → Notifications → выбор типов → OS grant → device binding → подтверждённые preferences. Отказ/ошибка на любой ступени не ломает участие. ## Навигационный контракт реализации - Expo Router route groups: `(public)`, `(account)`, `(auth)`, без security reliance на названия групп. Backend проверяет каждое действие. - Intended destination: allowlisted route kind + entity ID + безопасные filters; никаких произвольных URL и credential query. Одно pending intent, expiresAt, transaction ID, account expectation при private destination. - Native navigation state не хранит access/refresh token. Handoff verifier — secure storage с TTL, используется одной transaction. - После auth: восстановить identity → перечитать target и существующий participation → показать confirm. Параллельный duplicate tap блокируется одним in-flight command и серверным idempotency. - Различать profile city, discovery city и game city/timezone. «Назад» не меняет один из них молча. - При back/escape из auth cancel transaction; после уже успешного входа close ведёт к продолжению, не разлогинивает. - Android hardware back / iOS swipe: вернуть предыдущий экран; confirm exit draft только при действительно несохранённых изменениях, без обязательных повторных onboarding. Кликабельная модель покрывает вышеуказанные ветки на fixtures. Реальные deep links, auth callback, durable process restart, hardware back и push delivery требуют M2/M5; HTML не является их доказательством.