← На главную

Разработчики запретили разрезать эмодзи в совместных редакторах

16.05.2026 12:49 · hackernews

Блогер, занимающийся разработкой совместных редакторов, рассказал историю о страшном баге, который заставлял исчезать введенный текст. Проект использовал библиотеку TipTap поверх ProseMirror и CRDT-механику Yjs для синхронизации данных. Иногда редактор молча переставал сохранять изменения. Пользователи продолжали печатать, интерфейс выглядел стабильным, но правки не отправлялись на сервер. При следующем открытии страницы весь текст после момента сбоя исчезал. Углубленное расследование показало, что проблема возникала при вводе определенных эмодзи, например, кругов 🟢 и 🔴, которые использовались в статусных письмах. Проблема появлялась, когда один из этих эмодзи копируется и вставляется рядом с другим, вызывая корректное редактирование текста. Дело было в Unicode. Эмодзи выше U+FFFF хранятся в JavaScript как пара из двух юнитов, называемая парой-суррогатом. Обычные символы занимают один юнит, а сложные эмодзи вроде женщины-астронавта 👩‍🚀 занимают пять юнитов, так как состоят из нескольких кодовых точек. Функция slice в JavaScript оперирует именно кодовыми юнитами. Если попытаться разрезать строку посередине пары-суррогата, получится два «сиротских» юнита, которые не имеют смысла. Попытка преобразовать такой сломанный символ в URL с помощью encodeURIComponent выбрасывала ошибку URIError, которую система обхвата не перехватывала, из-за чего процесс синхронизации в библиотеке lib0 (которую использует Yjs) просто останавливался. Разработчики внедрили два временных решения. Во-первых, добавили офлайн-режим, позволяющий накапливать правки локально. Во-вторых, применили радикальный метод: повесили глобальный обработчик ошибок, который ловил конкретное исключение URIError и вылезал пользователю окно с просьбой перезагрузить страницу. Это сработало, хотя баг все еще существовал. Настоящее исправление пришло позже. В библиотеке lib0 сделали патч, который проверяет наличие первого суррогата без пары и сразу заменяет его на спецсимвол замены. Кроме того, в TipTap сделали эмодзи отдельным типом узла. Теперь редактор воспринимает каждый эмодзи как неделимый объект, и операции редактирования больше не могут расколоть его пополам. Теперь для корректной работы с символами в JavaScript настоятельно рекомендуется использовать класс Intl.Segmenter с параметром granularity графемы. Он корректно разделяет строки на отдельные графемы, игнорируя внутреннее строение UTF-16. Проблема остается актуальной: любой код, использующий slice или прямой индекс для получения первого символа, может сломаться при работе с эмодзи или знаками за пределами основной мультимедийной плоскости.

Читать оригинал →