Статья исследует сложности создания блочных редакторов, которые выходят за рамки простого редактирования текста, подобно Notion. В отличие от традиционных текстовых полей, где Enter означает новую строку, в блочных редакторах он может инициировать разделение блока, сохраняя форматирование и обеспечивая целостность данных. Документ рассматривается как упорядоченный список структурных элементов (параграфов, заголовков, цитат и т.д.), каждый из которых обладает собственной идентичностью, жизненным циклом и правилами сериализации. Операции, такие как нажатие Enter для разделения блока или Backspace в начале блока для его слияния, являются сложными действительными операциями, а не просто локальными текстовыми правками.
Предлагается архитектура, основанная на разделении ответственности за состояние между различными компонентами. Ключевой принцип заключается в том, что каждая область состояния должна иметь одного владельца, чтобы избежать конфликтов и ошибок. Это достигается путем разделения состояния на неизменяемый снимок (snapshot state) и долгоживущие объекты редактирования в реальном времени (runtime editing objects). Проблема возникает, когда эти две системы пытаются одновременно управлять одним и тем же состоянием, что приводит к ошибкам синхронизации, неправильному перемещению курсора и некорректному применению форматирования.
Рассматривается антипаттерн, где UI синхронизируется с состоянием редьюсера с помощью эффектов (effects). Этот подход терпит неудачу, когда пользователь активно редактирует текст, так как живой буфер редактирования в UI становится авторитетным источником, а последующая синхронизация из редьюсера может перезаписать изменения или некорректно установить курсор. Это приводит к так называемым «ошибкам порядка» (ordering bugs), когда отдельные операции валидны, но выполняются в неправильной фазе конвейера.
Архитектура предлагает три основных владельца состояния: состояние снимка (snapshot state), буфер редактирования в реальном времени (runtime editing buffer) и намерение форматирования (pending styles). Важно, что выделение текста (selection) разделено: блочное выделение принадлежит снимку, а выделение внутри блока — буферу редактирования. Намерения форматирования, такие как «следующий символ должен быть жирным», относятся к редактору как обещание для следующей вставки, а не как часть текущего состояния документа.
Операция разделения (split) служит доказательством работоспособности модели владения. Она затрагивает все области состояния одновременно, и порядок выполнения операций критичен. Например, разделение текста должно происходить на основе живого буфера, а не снимка, чтобы сохранить корректное форматирование. Стили, применяемые к новой части блока, должны быть определены до того, как исходный контекст будет очищен.
Операция слияния (merge) является инверсией разделения, но имеет свои особенности. Она может потребовать восстановления живого состояния для обоих участников слияния, если один из них еще не был отрисован. Текст и стили объединяются в буфере реального времени, после чего состояние снимка обновляется. Это подчеркивает асимметрию: для разделения важен живой источник, для слияния — возможность материализовать живое состояние для обоих блоков.
Статья также обсуждает, как обрабатывать текстовые эффекты. Вместо того чтобы реагировать на намерения ввода (input intent) напрямую, редактор сначала применяет изменения к буферу, а затем наблюдает за зафиксированным состоянием (committed state). Это позволяет отделить логику форматирования от поведения IME и времени выполнения обратных вызовов. Особое внимание уделяется обработке программных изменений (programmatic edits) для предотвращения двойного применения изменений форматирования.

