Представьте: вечером вы открыли группу, в которой за день накопилось пятьдесят сообщений. Прочитали несколько последних, пролистали вверх и задержались на старом голосовом. На одном сообщении оставили реакцию. Что теперь считать прочитанным — все пятьдесят, всё до последнего просмотренного или только те сообщения, которые действительно попали на экран?
Ответ влияет сразу на несколько вещей: на цифру рядом с чатом, на отметку у отдельного сообщения и на подробную статистику. Чтобы эти сигналы не спорили друг с другом, системе приходится различать открытие чата, визит в него и подтверждение просмотра конкретной записи.
Открытие, визит и просмотр — разные события
Открытие чата говорит только о том, что перед вами появилась переписка. Вы могли сразу отвлечься, не прокрутить её до старых сообщений или открыть чат на пару секунд, чтобы проверить имя собеседника. Поэтому само открытие не доказывает, что вы прочли всё, что в нём накопилось.
Последний визит нужен для другой задачи: он проводит границу между сообщениями, которые уже были в чате к моменту фиксации визита, и теми, что пришли позже. Если после визита кто-то напишет, новое появится за этой границей. Но визит не стирает отдельные пробелы в чтении старой истории.
Просмотр — точечное подтверждение: конкретное чужое сообщение оказалось в видимой части открытой переписки. Позднее сообщение не служит закладкой, которая автоматически «прочитает» всё до него. Если вы перешли прямо к вчерашнему ответу, это не означает, что каждое сообщение между ним и сегодняшним днём тоже было у вас на экране.

В списке чатов заметный значок с числом показывает новые непрочитанные сообщения. Отдельно существует полный счётчик непрочитанного: он может включать более ранние сообщения, оставшиеся без подтверждения просмотра. Поэтому отсутствие значка новых сообщений не всегда означает, что в старой переписке больше нет непрочитанных записей.
Что нужно, чтобы просмотр подтвердился
Приложение не принимает кивок в сторону экрана за прочитанное. Оно проверяет, что переписка сейчас действительно открыта и активна, страница видна, список сообщений уже занял своё место, а карточка конкретного сообщения оказалась внутри рабочей области прокрутки. Сообщения автора, системные записи и черновики в этот учёт не входят.
Зачем столько оговорок? При загрузке список может на мгновение перескочить, карточка — пройти мимо из-за восстановления позиции, а соседняя вкладка — оказаться поверх той, что вы видите. Подтверждение ждёт, пока экран стабилизируется и сообщение можно действительно рассмотреть. Несколько таких подтверждений приложение отправляет вместе, но каждое сохраняет свой точный номер.
Как множество просмотров превращается в несколько диапазонов
В примере ниже сообщения 101, 102 и 103 уже показались читателю. За ними он пропустил 104, затем просмотрел 105, 106 и 107. Удобно хранить три номера слева одним отрезком и три справа другим. Сообщение 104 остаётся отдельным пробелом, а не теряется между ними.
Диапазон экономит место, но не означает «прочитано всё между двумя известными точками». Если посередине остаётся сообщение, которое должно считаться непрочитанным, диапазоны сохраняются раздельно. Это похоже на закладку между несколькими прочитанными страницами: короткая запись о границах удобна, пока она не стирает страницу, которую читатель пропустил.
Есть и безопасные промежутки. Например, между двумя просмотренными сообщениями численно могут оказаться уже удалённые записи, собственные сообщения или системные отметки. Если там не осталось непросмотренного входящего сообщения, препятствовать объединению незачем. Система может постепенно проверить и такой промежуток в фоне. Пока вся его длина не проверена, диапазоны не склеиваются; проверка меняет только компактность записи, а не то, что человек действительно видел.
Последний визит отмечает границу новых сообщений
Когда активный участник входит в личную переписку, группу или канал, сервер фиксирует для этого чата границу визита. Она связана с последним сообщением чата на момент визита и хранится у участия именно в этой переписке. В следующий раз новые сообщения сравниваются с ней, а каждое фактическое подтверждение по-прежнему сверяется отдельно.
Поэтому последнее посещение чата не равно присутствию пользователя где-то в приложении. Оно не говорит, что человек сейчас онлайн, видел уведомление или прочитал старую историю. Это локальная отметка: «в этой переписке я дошёл до такой границы». Старые непрочитанные сообщения могут остаться в полном счётчике, хотя в число новых для бейджа они уже не входят.
Факт просмотра переживает время; точное время — нет
У просмотра есть два разных слоя. Первый отвечает на вопрос «видел ли этот участник это сообщение?». Такой факт хранится постоянно, пока существуют соответствующие чат и сообщение и доступные для учёта данные. Второй слой отвечает на вопрос «когда именно это случилось?». Точная дата и время первого просмотра хранятся семь суток, затем временная отметка удаляется.
Эти сроки не противоречат друг другу. Можно перестать хранить часы и минуты, но оставить ответ «да, просмотр был». Истечение семи дней поэтому не уменьшает счётчик просмотревших, не передвигает границу непрочитанного и не заставляет старое сообщение снова выглядеть непрочитанным. Меняется только доступность точного времени в подробностях.
Прослушивание относится к голосовому вложению
У голосового сообщения есть собственная история. Подтверждённое прослушивание привязывается не просто к сообщению целиком, а к конкретному голосовому вложению в нём. Поэтому число рядом с волной показывает прослушивания именно этой записи. У другого вложения — свой счётчик и свои участники.

Когда запуск воспроизведения подтверждён, сервер записывает событие прослушивания и его время. Это подтверждение работы плеера, а не свидетельство, что запись дослушали до конца. Повторное прослушивание не создаёт ещё один персональный факт, не прибавляет второе прослушивание к общему счётчику за всё время и не заменяет время первого раза. В старых записях, созданных до появления постоянной отметки времени, точной даты может не быть; восстановить её задним числом нельзя.
Подтверждённое прослушивание чужого сообщения может заодно подтвердить и просмотр самого сообщения. Это два разных ответа на два разных вопроса: «увидел ли человек карточку?» и «прослушал ли он вот это вложение?». Получить первый ответ только из счётчика прослушиваний нельзя — эти факты учитываются отдельно, хотя один поступок иногда создаёт оба.
Одно сообщение, два голосовых и две истории
Вот необычный случай из редактора. Допустим, сообщение с номером 58 уже видели несколько участников, а голосовое A успели прослушать. Автор оставляет текст на месте, но удаляет A и записывает новое голосовое B. Для читателя это всё ещё та же реплика в переписке. Для статистики вложений — уже другой объект.
Когда старое вложение удаляется при сохранении сообщения, исчезают и привязанные к нему факты прослушивания. Новое вложение получает новый идентификатор, поэтому его счётчик и список прослушавших начинаются с нуля. История просмотра сообщения не стирается: номер самой реплики остался прежним.
Если автор изменил только текст и оставил прежнее голосовое вложение, сохранённый идентификатор аудио тоже остаётся прежним — вместе с ним сохраняются его прослушивания и время первого подтверждённого прослушивания. Такая разница не видна по одним только словам «сообщение отредактировано»: важно, заменили саму запись или лишь поменяли текст рядом с ней.
Реакция хранит только своё текущее состояние
Реакция тоже может подсказать, что участник обратил внимание на конкретное сообщение, но устроена иначе, чем просмотр. Для неё важны выбранный emoji и время, когда реакцию поставили или изменили. Если заменить один знак на другой, время обновится: это новое подтверждённое действие, а не просто другой способ показать старое.
Снятая реакция больше не является текущей отметкой. В отличие от времени просмотра, у неё нет семидневного срока: реакция остаётся, пока её не заменят или не снимут. Если человек позже поставит её снова, у нового состояния появится новое время. А если сеть повторно отправит тот же запрос из-за обрыва связи, сервер распознает его как повтор попытки: счётчик и порядок действий не должны сдвинуться второй раз.
Подтверждённое действие с реакцией на чужом сообщении также может зафиксировать просмотр этой реплики. И снова это не значит, что одно заменяет другое: просмотр остаётся фактом о сообщении, а реакция — текущим ответом человека на него.
Что может показать подробная статистика
В группах участнику с подходящим текущим доступом может быть доступен персональный список подтверждённых действий с сообщением. Он объединяет сведения о просмотре, прослушивании отдельных вложений и текущей реакции. Участники располагаются по последнему сохранённому действию: недавний просмотр или первое прослушивание поднимает человека выше, а новая реакция меняет его позицию.

В каналах задача другая: подписчиков может быть много, и подробный персональный список читателей там не предоставляется. Вместо него доступны агрегированные числа просмотров сообщения и прослушиваний вложения, а реакции сгруппированы по emoji. Число «5» и перечень пяти конкретных людей — не одно и то же, даже если сейчас оба ответа случайно совпали.
Постоянный общий счётчик тоже не равен числу имён в списке, который можно открыть сегодня. Список ограничен текущими правами и доступностью истории; его отображение может меняться, если удалено сообщение или вложение, изменился доступ участника либо исчезли связанные данные аккаунта. Поэтому система не обещает, что личность каждого просмотревшего будет всегда доступна всем и при любых обстоятельствах.
Почему здесь нет одного общего «прочитано»
Ни открытый экран, ни последняя отметка о визите, ни число у голосового по отдельности не описывают весь разговор. Вместе они дают более аккуратную картину: граница визита отвечает за новые сообщения, точные просмотры — за отдельные карточки, вложение — за конкретное прослушивание, а реакция — за то, что человек оставил видимым сейчас.
Та же идея — привязывать действие к конкретному объекту — в принципе может пригодиться новым форматам медиа. Например, у будущего видео может появиться собственная статистика просмотра. Это лишь возможное развитие модели, а не описание уже работающей функции.