Чатбот выглядел как ICQ
Открываю чат на сайте. Жму кнопку, пишу сообщение. Ответ приходит. Только выглядит всё как одна простыня текста. Где бот, где я, понять можно только по аватарке «IM» слева. Привет, 2003 год.
Решил привести в порядок. Не «переписать с нуля на React с Framer Motion». А нормально расставить акценты за пару часов.

Что было не так
Проблем хватало:
- User bubble и bot bubble визуально одинаковые. Оба серые, оба одного размера, оба с аватарками. Разница только в выравнивании, которое работает… когда стили применяются
- Аватарка пользователя справа - SVG-человечек в кружке. Лишний шум. Правое выравнивание и акцентный цвет уже говорят «это я написал»
- Input и кнопка отправки живут отдельно. Два независимых элемента с gap между ними. Выглядит как форма из учебника
- Бот не умеет в нумерованные списки.
renderMarkdownобрабатывает буллеты и bold, а1. itemпревращается в обычный параграф
Семь изменений
Пузыри. Сообщения пользователя получили полупрозрачный фон - мягкий акцент вместо ослепляющего белого. Сообщения бота - тонкую левую границу как визуальный якорь. Ширину обоих ограничил до 75%, чтобы не расползались на весь экран.
Аватар бота. Чуть увеличил, добавил обводку, прижал к верху сообщения. «IM» перестал болтаться внизу длинного ответа.
Аватар пользователя. Удалил. Полностью. Правое выравнивание и акцентный цвет и так говорят «это я написал». Чище, тише, яснее.
Поле ввода. Объединил текстовое поле и кнопку в один визуальный блок. Общая рамка, общее скругление. При фокусе подсвечивается вся обёртка. Разделитель заменил на лёгкую тень сверху.
Нумерованные списки. Бот теперь умеет отвечать нумерованными списками, а не только буллетами.
Отступы. Последнее сообщение больше не прилипает к полю ввода.
Доступность. Форма отправки получила подпись для скринридеров.
Баг который сломал всё
Деплоил. Открыл. Ничего не изменилось. Вообще ничего. Те же серые прямоугольники, тот же поток текста. Как будто ни одного CSS-правила не применилось.
Потому что не применилось.
Astro по умолчанию делает стили локальными - они применяются только к элементам, которые существовали при рендере страницы. А сообщения чата создаются динамически через JavaScript. Astro их не знает, стили до них не доходят. Тишина.
Фикс - одно слово: <style is:global>. Селекторы и так достаточно точные, утечки стилей нет.
Этот баг жил в чатботе с первого дня. Tailwind-классы в разметке работали (они глобальные), а вот CSS-правила для динамических элементов молча игнорировались. Чат выглядел как простыня не из-за плохого дизайна - а потому что дизайна буквально не было. Браузер показывал элементы без единого кастомного правила.
oklch вместо rgb
Почему oklch? Старый формат rgba - это четыре числа, смысл которых понятен только автору. В тёмной теме приходится угадывать значения наощупь. oklch разделяет яркость, насыщенность и прозрачность. oklch(0.985 0 0 / 0.18) читается как «почти белый, без цвета, 18% видимости». Понятнее и предсказуемее в обеих темах.
Безопасность
Каждый раз когда трогаешь фронтенд с innerHTML - стоит проверить. Прошёлся по всем изменениям:
- Рендер маркдауна экранирует спецсимволы до обработки - HTML-инъекции исключены
- Сообщения пользователя вставляются как текст, не как HTML
- Глобальные стили не открывают CSS-инъекций - все значения захардкожены
- Нумерованные списки парсятся тем же безопасным способом что и буллеты
Дыр нет. Проверил.
Итого
Два коммита. Один файл. CSS который наконец-то работает. Чатбот перестал выглядеть как лог в терминале и начал выглядеть как чат.
Самый интересный баг - не в коде. А в предположении что «раз элемент на странице, стили до него дойдут». В Astro дойдут. Но только если элемент существовал на момент рендера. Остальным нужен пропуск.
Читайте также


