# #398 · R2 — explicit activation / одна лента Reviewable design fixture, **не source implementation**. Открыть `index.html` локально; все ресурсы локальные. Галерея содержит работающий iframe и 8 прямых ссылок. `viewport` в URL — метка capture, реальный viewport задаёт оператор. На странице прототипа пульт находится **вне продукта**, после ленты; ссылка сверху ведёт к нему. Нажать Radar → в пульте передать consent/geo → подтверждение ON → первый READ. Нет сетевых вызовов, разрешений, таймеров, координат или реальных профилей. Авторство: текущий native R2 call, маршрут **gpt-6-astra / HIGH по trusted assignment**; без подагентов и вложенных model/provider calls. Фактический launch receipt подтверждает внешний оператор; здесь нет новой qualification или измерения расходов. На завершение авторского вызова PNG и независимая QA ещё не выполнялись. Внешние результаты приведены ниже. Продуктовый UAT остаётся NOT_TESTED. V1 `radar398-loading-astra-20261109` и `radar398-implementation-capsule-20261109` — **HISTORICAL / SUPERSEDED** для новой UX. Старые файлы/receipts не изменены; technical fences сохраняются. Read-only reference: `568fe0443064a54e3dfe198f20a706258245cbb9`, accepted68 tree alias `2c33ce49d5b8ad704bf78524b8149c94e4d3e62e`. Future source admission требует свежей base-проверки; данный ref не объявляется текущим DEV. ## Визуальный scope Один вариант: тонкая статичная строка **«Ищем ближайших игроков…»**, затем полное удаление строки. Лента остаётся доступной во время поиска. При 0 Radar: «Участников Radar пока нет.»; при ошибке/UNKNOWN: «Не удалось проверить Radar.» + «Статус Radar» — существующий `RadarStatusAffordance` (`RadarSessionProvider.tsx:207`) остаётся владельцем recovery. Клик fixture показывает пояснение вне продукта, не новый overlay. Нет постоянной панели, счётчика, Nearby/Ordinary headings, иллюстрации, обещания всех городов или оптимистического ON. Шапка, фильтры, map CTA, карточки показаны схематично и исключены из реализации R2. Геометрия карточек взята из `apps/web/src/components/consumer/radar/UnifiedPlayerCard.tsx` → `../players/ProfileComposition.tsx:35` и `apps/web/src/styles/players.css:47`: identity grid, avatar 56/46, padding 20/17, 2/1 колонки. Профили A–D синтетические; loaded использует B,C,A,D как заданный fixture-ответ с authorized origin и radar_distance_then_directory. Округлённые ≈2/3 км — явно вымышленные значения примера, без координат. Отдельный loaded-city / ready_city показывает originState=none и городской порядок с неизвестным расстоянием. Клиентская сортировка отсутствует. Статичность безопасна для Reduce Motion; focus, цели ≥44px, safe area, rem и естественный перенос при **реальном font-size:200%** доступны. Карточки/карта схематичны, не создают новые overlay. STYLE_GUIDE_PARITY: локальный `/opt/gobadminton-clean-qa/style-guide/src/index.css`, Onest, ink/paper/lime, toggle, PlayerCard, loading/notice primitives. Bounded extension — компактная статичная inline loading строка; OWNER_VISUAL_OVERRIDE — отмена постоянной V1 панели. Существующие logo/Onest скопированы без генерации. ## Display-generation contract / минимальный handoff `explicit ON → armed (невидимо) → legitimate activation → loading → first matching READ → removed | short fallback` 1. **Вход отсутствует сейчас.** `apps/web/src/components/consumer/radar/RadarControls.tsx:50` (`RadarSwitchControl.onToggle`) вызывает `permission()` либо `stop()`, без callback. Предложение: optional `onExplicitIntent('on'|'off')`, только из действительного enabled жеста. Прокинуть через `apps/web/src/components/consumer/players/PlayersDirectoryToolbar.tsx:118` и прямой mobile call в `apps/web/src/components/consumer/pages/PlayersDirectoryPage.tsx:379`. Local owner — `PlayersDirectoryPage`; не event bus/backend. Default ON-button также должен идти через тот же guarded handler, если callback передан. Rejected permission не оставляет armed token. 2. Token: монотонная `displayGeneration` + текущие controller instances + actor/session identity; затем confirmed `activationId:version`, `filterKey`, принятый first-read token. Никаких credentials в UI/URL/storage. Только explicit token разрешает наблюдать дальнейшие фазы: mount already ON, focus, background, preference/filter changes, reconcile и reused busy **не создают token**. Existing `RadarSessionProvider.tsx:363–375` владеет единственным permission `ProfileDialog`; его consent/geo flow неизменен. Начать видимый поиск лишь после consent/geo, на допущенной `activating` в этой цепочке; переход фазы сам по себе не intent. Cancel/denial/rejected admission завершают armed без поискового обещания. 3. ON всегда берётся из `RadarView.confirmedOn`. `confirmedDirectoryActivation` в `apps/web/src/lib/radar-controller.ts:14` допускает только active/current, без pendingStop. `RadarSessionProvider.tsx:52` задаёт `setResultView('none')`: **не использовать `RadarView.loading`**. Page `:96–130` уже задаёт query city/filters/current_activation и refresh gates. Первый свежий текущий `DirectoryView.snapshot`, прошедший `renderable(filters, activationKey)`, завершает loader сразу, включая zero; не ждать map, pagination или всех Radar rows. 4. **Корреляция READ — точечный source gap.** `DirectoryView` имеет snapshot/rows/busy/notice/stale/auth/capability, но нет request ID / typed ready-error; `query()` возвращает `Promise`, early skip не отличим от settlement. Минимальное предложение для source admission: optional lifecycle observer на существующем `UnifiedDirectoryController.query` (`apps/web/src/lib/unified-directory.ts:317–383`): dispatched/settled/skipped с локальным read token и captured scope. Привязать его лишь к armed generation, когда реально принят первый query. Не привязываться к чужому pending/busy. Matching failure/invalid snapshot/unknown прекращают loader; skipped cooldown даёт короткий fallback, не бесконечное ожидание. Повторы только через существующие `nextRefreshAt`, pending, `retryAt`/Retry-After — без нового retry-button bypass. 5. OFF, actor/controller replacement, unmount, revoke, filter-scope change инвалидируют display token; старые callbacks не могут вернуть строку. Reconcile/background не возобновляют её. Это **не отмена отправленной mutation**: `RadarController.cancel():140` не отменяет dispatched write; UNKNOWN сохраняется и не replay. Existing stop/reconcile/identity/TTL/privacy semantics неизменны. 6. **Deadline gap, не скрытый PASS:** `apps/web/src/lib/api-client.ts:140` — native fetch без default deadline. Первый READ — семантически чтение, но HTTP **POST `/v1/players/query`**, с существующим AbortSignal, без конечного deadline. При никогда не settled Promise текущий source не гарантирует конец loader. До source admission parent выбирает допустимый bounded first-READ механизм в существующем transport; здесь нет выдуманной длительности, таймера или новой отмены POST. Для unresolved activation write лишь display fallback по допустимому сигналу, без вывода о фактическом ON и без abort/replay. ## Порядок, fences и приёмка `unified-directory.ts:61–78` сохраняет серверный порядок `view.rows`, live TTL и dedupe по `publicPlayerId`; Page `:567–569` уже выводит radar → существующая More boundary → ordinary в **одном** каталоге. Не удалять boundary/pagination. `apps/api/src/radar/unified-selection.ts:60–102`: eligibility/privacy, rank **до LIMIT** по `group_key,distance_key,name_key,publicPlayerId`. `apps/api/src/radar/unified.service.ts:365–370,434–437`: distance только при origin+dist_m; `query.ordering=radar_distance_then_directory` и `originState=authorized`, иначе `radar_city_then_directory`/`none`; поля уже в `packages/contracts/src/unified.generated.ts`. Query ограничен eligible city; ordinary city selection не меняется. Ни client sort/георасчётов, ни backend changes. Map/Swipe и один существующий overlay owner сохраняются. Оператор: capture 8 URLs из плана с loaded fonts; затем keyboard/200% text/Reduce Motion и независимый review. Targeted future source checks: explicit ON только; pending ON не рисуется; matching ready/0/failure; OFF/actor/unmount late result; reused busy/filter/focus не arm; cooldown сохранён; hanging first READ после выбранного deadline-механизма. Проверить один список/dedupe/More и отсутствие скрытых карточек. Авторская проверка артефакта не заменяет QA/CI/DEV acceptance. Source author не запускался. ## Внешняя проверка и адресные исправления R2 Оператор проверил 10 сочетаний состояния/размера и шесть взаимодействий на390/1280, снял восемь PNG. Ошибок JS и переполнения нет. [Результат](operator-browser-result.json). Независимый review обнаружил два несоответствия fixture, исправленных без новой концепции или model call. Физический OFF теперь инвалидирует display generation и сохраняет confirmedOn/pendingStop до явного синтетического stop_confirmed; stop_unknown сохраняет неопределённость. Reset остаётся отдельной внешней командой макета. Основной loaded теперь явно демонстрирует поддержанный authorized distance-order, отдельный city-order сохраняет честный fallback. Внешние existing_on/scope_change показывают вход без loader и инвалидацию без оптимистического OFF. [Исправления](operator-repair-receipt.json) · [Независимый отчёт](independent-review.md). Публикационный whitelist с QA-доказательствами фиксируется в design-manifest.json. V1 и его прежняя implementation capsule сохраняются исторически; при будущем source admission применяется R2 и свежий base CAS. Прямое Owner поручение не требует дополнительного design approval gate. Для показа Owner стартовая галерея открывает idle. Инструменты выбора состояния и пульт ответов макета по умолчанию свёрнуты и обозначены вне продукта. После нажатия Radar открывается только подписанный пульт тестовых ответов для ручного consent/geo; ответы сервера автоматически не подставляются.