Conversation
Фоновая вкладка глушит requestAnimationFrame, и анимации, которые ВЕДУТ элемент
из скрытого состояния в рабочее, замирали на полпути:
- Sheet: entering={SlideInDown} оставлял панель под вьюпортом (translateY 613px,
скрим 0.069) — шторка «не открывалась», а её полноэкранный скрим продолжал
глотать клики, из-за чего приложение выглядело зависшим;
- CountUp: заголовок «Ожидают оплаты» застревал на 548/630 вместо 6 000.
Вход шторки переведён на обычный shared value с дедлайн-таймером: таймеры тикают
и без кадров, поэтому незавершённый вход роняет панель в статичную позицию покоя.
Счётчик получил такую же страховку до финального значения.
Тест на согласованность: заголовок = Σ бакетов «По сроку» = Σ списка должников.
Экран уведомлений приведён к эталонному прототипу: вкладки read-состояния теперь «Все / Непрочитанные», а тип события выбирается строкой чипов под ними («Все · Уроки · Оплата · Расписание · Система») вместо спрятанной за воронкой модалки. «Прочитать всё» и иконка «Настройки» в шапке сохранены — они не спорят с моделью эталона и полезны на длинной ленте. Вкладка «Прочитанные» = «Все» минус «Непрочитанные», отдельной вкладки не нужно. Ключи notif.tab.read / notif.filterType / a11y.notifFilter удалены вместе с модальным фильтром.
Сессия хранилась только в памяти, поэтому F5 на внутреннем экране и прямой переход
по ссылке выбрасывали на /sign-in. Флаг переехал в единственную строку profiles
(schema v12, signed_in, nullable → null читается как «не вошёл»), ProfileGate
поднимает его ДО монтирования дерева и отдаёт в AuthProvider — гейт больше не видит
ложного кадра «не авторизован» и не уводит с запрошенного маршрута.
Следом лечится и «назад» после deep-link: стек начинается сразу с детального экрана,
router.back() там отвечает «The action GO_BACK was not handled» и оставляет юзера на
месте. Общий useBack()/backOrHome() уходит на корень, когда возвращаться некуда.
Ещё из наблюдений теста:
- useStudent('') на первом рендере деталей урока ронял необработанную ошибку
«Record students# not found» — пустой id больше не подписывается, а ошибки
подписки логируются вместо вылета наружу;
- пустое имя профиля в настройках показывалось прочерком — теперь «Добавьте имя».
Занятие, по которому есть операция с деньгами, защищено от групповых правок (ADR-0016 §4) — scopeCancel/scopeReschedule для него не трогают ни одной записи. Экран при этом показывал «Урок отменён» и уводил назад: пользователь видел успех, а после возврата урок оказывался на месте. Теперь оба пути смотрят на affected: ноль — честный снек «По уроку есть оплата — изменение не применено» и остаёмся на экране; иначе прежний снек с «Вернуть» и возврат в расписание.
…причину По итогам ревью — два блокера. 1. Спасательный таймер шторки армился только после onLayout, а onLayout в react-native-web едет на ResizeObserver, который доставляется в том же шаге рендеринга, что и requestAnimationFrame. В фоновой вкладке высота панели не приходит никогда — таймер не взводился, панель оставалась с opacity 0, а полноэкранный скрим по-прежнему собирал клики. Теперь высота — только уточнение дистанции (до неё FALLBACK_TRAVEL), а окно входа закрывает таймер, взводимый безусловно; после него панель переходит на drag-only стиль, который встаёт в позицию покоя без единого кадра. 2. affected === 0 трактовалось как «есть оплата», хотя домен отказывает ещё и по проведённому/отменённому занятию и по пустому окну охвата. Причина теперь выбирается по факту, а «Отменить» на уже отменённом уроке выключена.
…артует по поздней вёрстке Адверсариальное ревью ветки (6 дименшенов + рефьютеры) подтвердило 11 находок. Критично — src/db/scope.ts: при affected === 0 запись слота выполнялась всё равно. scopeCancel закрывал серию (activeTo = watershed), scopeReschedule переписывал время всей будущей серии (timeMin) — при том что вызывающий код показывает «изменение не применено» и в отказе НЕ предлагает «Вернуть». Пользователь терял регулярное расписание молча и необратимо: materializeSlots такой слот больше не берёт, а из экрана расписания он исчезает, то есть переоткрыть его через UI нельзя. Обе операции теперь выходят no-op'ом до записи. Sheet: измеренная высота панели переехала из состояния в shared value. Она стояла в deps эффекта, и её позднее прибытие (ResizeObserver голодает вместе с rAF) сносило уже спасённую таймером шторку — панель уезжала под вьюпорт и переигрывала вход, а невидимый скрим эти ~300 мс собирал тапы. Заодно исправлено объяснение механизма спасения в шапке: работает не «пересчёт стиля на каждом рендере», а смена идентичности стиля. CountUp: отложенный кадр после settle-таймера сбрасывал заголовок в 0 и пересчитывал — теперь гасится флагом. Мелочи: мёртвый ключ common.apply, устаревший комментарий про модальный фильтр.
Найдено ручным прогоном. Snackbar лежал в том же классе, что Sheet и CountUp, но не попал в первую правку: бар стартует прозрачным и на 14px ниже, а поднимала его связка onLayout + withTiming — обе кадрозависимые. Замер при нуле кадров: снек монтируется с верным текстом, живёт весь свой срок на opacity 0 и молча исчезает через 3.5 с. То есть подтверждения действия не видно вообще, а вместе с баром недоступно и «Вернуть» — единственный путь отката отмены урока и записи оплаты. Вход перенесён в эффект (ему кадры не нужны) и подстрахован дедлайн-таймером, как в Sheet. Строка чипов категорий в уведомлениях — регресс правки п. 7: ScrollView остался сжимаемым flex-ребёнком, и на достаточно длинной ленте его сплющивало до 18px, обрезая подписи чипов посередине. Зафиксирован flexShrink: 0.
Правки после теста 19–20.07.2026 — все 7 пунктов
Найдено ручным прогоном. Подпись фиксированной ширины 120 центрируется под точкой в слоте шириной 20, то есть свисает на 50 px в каждую сторону. Для средних точек это и задумано, но крайние точки прижаты к краям карточки, и там свис уходил наружу: замер в первом слоте давал левый край текста на −16 при границе карточки 33, в последнем — правый край на 202 при границе 153. На узком экране подпись срезало. Случай не краевой: следующий урок оказывается первой точкой каждое утро, пока не проведён ни один урок дня. Теперь у крайних точек свис разрешён только ВНУТРЬ — текст встаёт ровно по границе (33 и 153 в том же замере). Середина ленты не меняется.
Подпись «следующее в HH:MM» не вылезает за карточку
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Семь пунктов отчёта по правкам плюс три дефекта, найденных ручным прогоном.
Пункты отчёта
2/3. «Отметить оплату» и «Отменить» в деталях урока открывают свои шторки.
Корневая причина пунктов 1, 2, 3, 5
Кадровый цикл встаёт, когда вкладка или приложение в фоне (замерено 0 кадров за 10 с). Анимации, которые ВЕДУТ элемент из скрытого состояния в рабочее, замирали на полпути: шторка оставалась за экраном, а её полноэкранный скрим продолжал ловить клики. Вход шторки, счётчика и снека переведён на схему «анимация + страховочный таймер», где состояние покоя достижимо без единого кадра.
Дополнительно найдено ручным прогоном
Плюс защита от потери регулярного расписания: отказ операции по защищённому деньгами уроку больше ничего не меняет в слотах.
Проверки
tsc, expo lint, vitest (119 тестов), expo export — зелёные. Ручной прогон проведён на задеплоенном превью и локально.
Остаётся посмотреть глазами на устройстве: движение анимаций (в тестовой среде кадров нет).