Проекты02 / 05
Свадебная площадка перестала разрываться между календарями и почтой: пары записываются на просмотр с телефона, подтверждение приходит с личного ящика их менеджера, а запись сама попадает в систему площадки.
blackbrookbarns.co.uk

- Клиент
- Blackbrook Barns, свадебная площадка в Стаффордшире, Англия
- Роль
- Разработка: API, админ-панель, виджеты, автоматизация
- Период
- Май – октябрь 2026
- Стек
- TypeScript, Fastify, Drizzle, PostgreSQL, Next.js, Preact, Make, Heroku, Vercel
- Онлайн
- blackbrookbarns.co.uk
Blackbrook Barns — свадебный амбар между Личфилдом и Саттон-Колдфилдом, который снимают целиком. Команда продаж небольшая, а сайт на Webflow уже делал своё дело. Новый сайт никому не был нужен. Нужно было, чтобы работа за ним перестала быть ручной.
Цель была конкретной: чтобы никому не приходилось ни за кем бегать, копировать данные между системами и вести два почтовых ящика. Мероприятия остаются в Events 500, существующей системе площадки. Платежи остаются за рамками.
Отсюда жёсткое ограничение: всё должно было жить внутри чужой страницы, на сайте, который я не контролировал, и выдерживать всё, что эта страница вытворяет.
- 01
Запись на просмотр прямо на странице площадки
21-дневное расписание в Shadow DOM, чтобы CSS сайта и мой не могли задеть друг друга. Еженедельный график дежурств решает, кто проводит просмотр; пара видит, с кем встретится, выбирает время и получает экран подтверждения и письмо. Работает на ширине телефона.
- 02
API, где запись не может сорваться по чужой вине
Fastify, Drizzle и PostgreSQL. Сначала коммитится строка; почта и система площадки работают после неё и могут только записать сбой в лог. Плохой день у третьей стороны никогда не лишит пару слота.
- 03
Автоматизация писем
На запрос брошюры сразу уходит благодарность, а примерно через семь дней — одно напоминание записаться, которое пропускается, если запись или запрос от пары уже есть. Контактная форма получает подтверждение. Письма уходят с ящика ответственного менеджера, и ответы возвращаются этому же человеку.
- 04
Одна админ-панель
Next.js: сетка свадебных дат на три года, календарь просмотров, доступность каждого менеджера, график дежурств с отпускным переключателем «всё на X», аккаунты команды и неотправленные уведомления. Забронированная свадьба сама закрывает этот день для просмотров.
- 05
Events 500 без ручного ввода
Каждая запись сама появляется в системе площадки с пометкой, откуда пришла: сайт, чат-бот или письмо с брошюрой.
- 06
AI-ассистент на сайте
Чат-ассистент на сайте отвечает на вопросы о помещениях, вместимости, ценах Twilight и просмотрах из базы знаний на 13,9 КБ и направляет пары на страницу записи.
Как это устроено
Пара
- Страница площадки на Webflow
- Виджет записи40 КБ, Shadow DOM
- Нативные формыподключены через вебхук
Ядро
- API36 путей
- PostgreSQLарбитр для каждого слота
После коммита
- Почтаподписанный вебхук → Make → ящик
- Events 500односторонняя отправка
Команда
- Админ-панель
- График дежурствкто отвечает за какие часы

То, что обычно не попадает в портфолио. Всё это было на самом деле, и всё это есть в истории коммитов.
Сбой 01
Полоса прокрутки, на которую ушло пять исправлений
- Симптом
- Страницы с виджетом застревали. Парам не удавалось докрутить до формы записи или футера: прокрутка упиралась в 1 470 из 2 636 пикселей.
- Причина
- На сайте работает плавный скролл Lenis, который кэширует высоту страницы при старте. Виджет, выросший позже, для него невидим. Каждое исправление находило слой глубже: событие resize, отправленное слишком рано, опрос, который сдавался слишком быстро, а потом выяснилось, что Lenis вообще игнорирует resize, когда сайт жёстко задаёт высоту html.
- Решение
- Lenis держит свой экземпляр приватным, поэтому виджет ровно на один кадр перехватывает метод в его прототипе, ловит живой экземпляр в момент вызова из рабочего цикла, возвращает метод на место и вызывает resize() при каждом изменении высоты. Этот код живёт внутри бандла: скрипты в футере, на которые он раньше опирался, позже исчезли с сайта. Страница, в которую ты встраиваешься, тебе не принадлежит.
Сбой 02
Интеграция с API, которого почти нет
- Симптом
- PDF вендора документирует один эндпоинт, и то с вырезанными справочными разделами. Поэтому я прощупал живой сервис и записал, что там есть на самом деле.
- Причина
expires_inзадокументирован как секунды, а возвращается ISO-таймстамп. Создание события не принимает дату. Чтение событий не возвращает идентификаторов. Нет ни обновления, ни удаления, ни вебхуков.- Решение
- Кэш токенов принимает обе формы срока действия. Дата, время и менеджер едут в поле заметок, чтобы запись была полезна команде. Отправка никогда не бросает исключений и идемпотентна по сохранённому ID. Edit и Delete в админке скрыты, хотя написаны и протестированы: до системы площадки они дойти не могут, а два расходящихся календаря хуже одной отсутствующей кнопки. Проверка отмен, когда она запускается, реагирует только на однозначный сигнал: ошибочно отменить настоящую встречу гораздо хуже, чем заметить отмену с опозданием.
Сбой 03
Летнее время, дважды
- Симптом
- В мае, в британское летнее время, упали первые тесты доступности: понедельник определился как воскресенье. Потом код-ревью нашло кое-что похуже в осеннем переводе часов.
- Причина
- Слот — это время по лондонским настенным часам, а сервер работает в UTC. Полночь в Лондоне во время BST — это 23:00 предыдущего дня, поэтому день недели выходил неправильный. А шаги по 24 часа через 25-часовой день 25 октября могли повторить дату и потерять день в окне записи.
- Решение
- В коде одна граница часовых поясов. Каждый день привязан к полудню по Лондону, а это всегда далеко от перевода часов. День недели читается из метки даты, никогда из момента времени. Три сквозных теста фиксируют осенний перевод, весенний перевод и обычную зимнюю неделю. Площадку бронируют за месяцы, поэтому календарь должен быть верным и в те даты, на которые никто не смотрит.
Сбой 04
Пусть арбитром будет база данных
- Симптом
- Две пары могут нажать на один слот в один и тот же момент; админ может дважды кликнуть Cancel. Ревью нашло тот же класс багов и в других местах, до продакшена.
- Причина
- Между проверкой и записью остаётся зазор. Самый странный случай: при повторном использовании refresh-токена код отзывал семейство токенов и тут же бросал исключение внутри той же транзакции, так что отзыв откатывался вместе с ошибкой. Повторный запрос получал 401, а семейство оставалось действующим.
- Решение
- Частичный уникальный индекс по менеджеру, дате и времени там, где статус confirmed. Запись — это обычный INSERT, который ловит конфликт и возвращает «слот занят»; проверено настоящей параллельной гонкой, победитель ровно один. Отмена — один условный UPDATE, поэтому письмо отправляет только победитель. Админ может обойти политику, но не индекс: конфликт — это не политика.
Сбой 05
Один неотправляемый адрес мог остановить всю почту
- Симптом
- Пары должны получать письма от своего менеджера, но почтовое подключение принадлежит одному человеку. Очевидное решение — поставить в From адрес этого менеджера.
- Причина
- Адрес, от имени которого подключение не может отправлять, роняет модуль, а упавший запуск может выключить мгновенный сценарий. Достаточно одного нового менеджера без нужного разрешения, чтобы молча остановились все подтверждения для всех.
- Решение
- В каждом пейлоаде отдельный отправитель: адрес менеджера, только если он есть в белом списке, иначе адрес по умолчанию, а Reply-To всё равно ведёт к менеджеру. Добавляем менеджера, и ничего не ломается. Ретраи пересчитывают отправителя из текущего конфига, так что сохранённый пейлоад не может воспроизвести отозванное разрешение. Проверено настоящей отправкой: строки «on behalf of» нет.
- автотестов в 62 файлахлог CI, 27 октября 2026
- зелёных прогонов CI с июняGitHub Actions
- каждый виджет в gzipReact заменён на Preact при сборке; сборка падает, если больше 50 КБ
- таблиц в базе данных36 путей API, 47 операций
- день в живом расписании записивидно на сайте площадки
- дней от первого коммита до последнего342 коммита; сделано с Claude Code
Пульс проекта
Каждый коммит в репозиториях проекта на одной линии. Чем выше столбик, тем жарче было.
- Репозиторий, требования, дорожная карта на шесть фаз
- CI, API, админка и виджеты на продакшен-хостинге
- Запрос на изменение: свой календарь у каждого менеджера
- Код для Events 500, после прощупывания живого API
- Отправка в Events 500 запущена
- Письма уходят с личного ящика менеджера
Сайт, который уже продаёт, не обязательно перестраивать. Внутри него могут жить небольшие, хорошо протестированные части, если сбой любой третьей стороны заложен в проект с первого дня. Если ваша команда всё ещё копирует данные между системами, с этой работы я бы и начал.
Или спросите о нём цифровой разум