Когда markdown убивает formatting: история трёх багов в Signal

Представьте себе: сообщение прошло через markdown-парсер, выглядит идеально в превью, но при рендеринге в Signal вдруг… смещение стилей, невидимые горизонтальные линии, списки прыгают по экрану. Именно эту головоломку решала команда OpenClaw в коммите #9781.
Три слоя проблем
Первый слой — markdown IR (внутреннее представление). Оказалось, что парсер генерирует лишние переносы между элементами списков и следующими абзацами. Вложенные списки теряют отступы, блокавроты выпускают лишние символы новой строки. Хуже всего — горизонтальные линии вообще молча пропадали вместо того, чтобы отобразиться видимым разделителем ───.
Второй слой — Signal formatting. Здесь затаилась коварная ошибка с накопительным сдвигом. Когда в одном сообщении расширялось несколько ссылок, функция applyInsertionsToStyles() использовала исходные координаты для каждой вставки, забывая про смещение от предыдущих. Результат: жирный текст приземлялся в совершенно неправильное место, как если бы вы сдвинули закладку, но продолжили считать позицию от начала книги.
Третий слой — chunking (разбиение текста). Старый код полагался на indexOf, что было хрупким и непредсказуемым. Нужно было переписать на детерминированное отслеживание позиции с уважением к границам слов, скобкам раскрытых ссылок и корректным смещениям стилей.
Как это чинили
Команда не просто закрыла баги — она переписала логику:
-
Markdown IR: добавили проверку всех случаев с пробелами, отступами, специальными символами. Теперь горизонтальные линии видны, списки выравнены, блокавроты дышат правильно.
-
Signal: внедрили cumulative shift tracking — отслеживание накопленного смещения при каждой вставке. Плюс переделали
splitSignalFormattedText()так, чтобы он разбивал по пробелам и новым строкам, не ломал скобки, и корректно пересчитывал диапазоны стилей для каждого чанка. -
Тесты: добавили 69 новых тестов — 51 для markdown IR, 18 для Signal formatting. Это не просто покрытие, это регрессионные подушки на будущее.
Факт о markdown
Markdown IR — это промежуточный формат, который сидит между текстом и финальным рендером. Он как сценарий между сценаристом и режиссёром: правильно оформленный сценарий экономит часы на съёмках. Неправильный — и режиссер тратит дни на исправления.
Итог
Баг был системный: не один глюк, а целая цепочка проблем в разных слоях абстракции. Но вот что интересно — команда не прошлась по нему топором, а аккуратно разобрала каждый слой, понял каждую причину, переписала на правильную логику. Результат: сообщения теперь форматируются предсказуемо, стили не смещаются, текст разбивается умно.
А коммит #9781 теперь живет в истории как пример того, как системное мышление побеждает импульсивные фиксы.
P.S. Что сказал Claude при деплое этого коммита? «Не трогайте меня, я нестабилен» 😄
Метаданные
- Branch:
- main
- Dev Joke
- Что сказал Claude при деплое? «Не трогайте меня, я нестабилен»