Все записи
8 мин

Астрология от нуля до MVP за 7 часов — и 16 косяков в UI, которых я сам не видел

claude codemvpBuilding in Public

За один день у меня случилось два разных дня. Сначала с шести утра до часу дня я собрал полноценный MVP астрологии для Картары — базу городов с поиском, расчёт натальной карты, интерпретацию через AI, чат, стриминг ответа и историю. Семь шагов процесса за семь часов, 118 тестов зелёные. А потом, ближе к вечеру, я открыл этот же интерфейс рядом с другими модулями приложения и насчитал шестнадцать расхождений в UI, который сам же и строил последние недели. И самое смешное тут даже не баги как таковые, а то, что один из них вообще не существовал как класс Tailwind, и мой основной агент его в упор не видел — увидел только второй ИИ.

Давайте по порядку, потому что тут две истории в одной, и обе по-своему полезные.

Семь шагов за семь часов

Контекст такой: за пару дней до этого у нас уже был готов свой движок расчёта эфемерид, и без него вся эта затея была бы либо невозможна, либо юридически мутной. Так что в это утро оставалось собрать вокруг него собственно продукт. Подхватил я работу в 06:06 на втором шаге из семи, и дальше пошло как по конвейеру — каждый шаг по отдельной спеке, с тестами, с ревью, с коммитом.

Если коротко по таймлайну, то шаг с базой городов закрылся к 06:23 — таблица с полнотекстовым поиском, плюс санитизация запроса от спецсимволов, которую нашёл security-агент, потому что без неё поисковый запрос можно было сломать обычной кавычкой. Шаг с калькулятором закрылся к 06:36 — обёртка над движком с матрицей уверенности и хешем снапшота, чтобы не считать одно и то же дважды. Эндпоинт расчёта закрылся к 06:47, и вот тут все три ревьюера независимо друг от друга нашли одну и ту же гонку: два параллельных запроса могли создать две записи, починили через нормальную обработку конфликта на уровне БД. Дальше пошли промпты домена, эндпоинт интерпретации и чата, и везде та же история — ревьюеры ловят то, что в потоке генерации проскакивает мимо.

И вот тут случился момент, который стоит проговорить отдельно. В 07:27 я увидел, что часть фич помечена как TODO вне scope MVP — стриминг, история, бенч-отчёт. Звучит вроде разумно, да? Сделаем потом, не в этой итерации. Но я перечитал спеку и понял, что это враньё, причём враньё, которое нейросеть сама себе придумала, чтобы закрыть задачу побыстрее. Все три эти штуки были прописаны как обязательное поведение, как критерии готовности конкретных шагов. То есть это не я хотел их отложить — это AI решил, что так будет аккуратнее, и тихо переписал scope под себя.

Поймал, вернул в работу, доделали к 07:45. И это, кстати, важная штука в том, как вообще устроена работа с агентом по процессу: без жёстко зафиксированных критериев готовности агент будет срезать углы не со зла, а просто потому что так оптимальнее закрыть тикет. Человек тут нужен ровно как тот, кто помнит, зачем вообще всё затевалось.

99 городов или пятнадцать тысяч

Параллельно был ещё один маленький, но показательный выбор. Для тестов хватало 99 захардкоженных городов — Москва, Питер, очевидная сотня. Но реальному пользователю Картары этого мало, человек живёт не в столице и хочет найти свой райцентр или какую-нибудь Йошкар-Олу. Так что заимпортили базу из GeoNames — 14563 города, из которых 92 уже были у нас. После деплоя я проверил вживую: дёргаю поиск по запросу «Москва», возвращается Москва с населением под тринадцать миллионов. Работает.

Это, кстати, к вопросу о том, чем «тесты зелёные» отличается от «фича готова» — у меня про это есть отдельная история. Можно иметь сто восемнадцать зелёных тестов и при этом базу из ста городов, на которой настоящий человек упрётся в стену через минуту. Поэтому я и требую end-to-end проверку на реальных данных, а не просто чтобы контейнер поднялся и healthcheck ответил «ок».

К восьми утра задеплоили на dev: git pull, четыре миграции, сборка, рестарт, сид городов прямо в контейнере. И на финальном ревью четыре агента параллельно нашли целый букет: список планет, который надо было сделать словарём, токены, которые списывались уже после ответа модели, хотя должны до, сессия БД, которая зря удерживалась во время стриминга, и старая уязвимость в авторизации, не связанная с астрологией, которую тоже тут же закрыли. Вот это всё — нормальная фактура одного утра вайбкодинга, когда код пишет нейросеть, а ты сидишь дирижёром и решаешь, что из найденного критично, а что подождёт.

Второй день внутри того же дня

А дальше начался второй слой. В 11:03 я открыл астрологию рядом с нумерологией и говорю: слушай, а почему оно выглядит по-другому? Загрузочный экран не такой, отступы плывут, акцентный цвет другой. И тут до меня дошла простая и неприятная вещь.

Каждый новый модуль в приложении я строил, копируя предыдущий с «мелкими правками». Таро скопировали с чего-то, сны — с таро, нумерологию — со снов, астрологию — с нумерологии. И на каждой итерации кто-то — я, агент, не важно — что-то чуть-чуть менял: тут отступ подвинул, там цвет перепутал, здесь захардкодил число вместо переменной. По отдельности это всё ерунда. Но копипаста с мутациями даёт накопительный разъезд, и через четыре модуля у тебя уже шестнадцать классов расхождений, которые никто специально не вносил — ровно так же у меня в своё время разъехались веб и Telegram Mini App. Я тогда написал ровно так: копипастить паттерн и надеяться, что не проебёшь отступ — это путь в ад, нужен общий каркас.

Запустил аудит всех четырёх модулей, и вот тут самое интересное место всей истории. Я гонял аудит двумя разными моделями параллельно — своим основным агентом и через Codex от OpenAI. И Codex нашёл то, что мой основной агент пропустил начисто: класс `pl-13` в вёрстке. А дело в том, что такого класса в Tailwind просто нет — дефолтная шкала отступов идёт 0–12, потом сразу 14, а тринадцати в ней не существует. То есть текст интерпретации во всех четырёх модулях рендерился вообще без отступа, потому что несуществующий класс молча игнорируется. Не падает, не ругается, просто тихо ничего не делает.

Я про это уже писал отдельно, но тут оно сыграло предельно наглядно: два инструмента, не согласные между собой, лучше одного уверенного. Один ИИ был уверен, что прошёлся по всему UI. Второй спокойно показал дыру. И если бы я доверился одному, я бы этот `pl-13` так и таскал из модуля в модуль ещё месяц.

Переписать всё или чинить точечно

Дальше встал вопрос, что с этим делать. Соблазн очевидный — раз уж UI разъехался, давай выкинем копипасту и сделаем один красивый общий компонент, через который всё будет рендериться. Но это ровно та ловушка, в которую соло-разработчик с AI попадает легче всего: «давай перепишем по-нормальному» превращается в неделю работы и новый слой абстракций, которые потом сами станут проблемой.

Поэтому я не стал решать в одиночку и собрал консилиум — восемь экспертов, четыре на стороне Claude и четыре на стороне GPT, со стресс-тестом и парой раундов дебатов. И вердикт получился взвешенный: новые абстракции не плодить, тут GPT был прав. Вместо большого рефакторинга — три фазы. Сначала точечно закрыть восемь конкретных багов. Потом вынести шесть тонких утилит и перевести на них три модуля. И только потом, если найдётся реальный дубликат больше сотни строк, бить уже его.

Так и сделали. Восемь точечных багов в десяти файлах — первый коммит. Шесть утилит и миграция трёх модулей на них — второй, и тут важна цифра: +342/−332 строки. То есть это баланс, а не раздувание, мы не плодили код ради красоты, а перекладывали существующий в общее место. А вот третья фаза дала уже настоящую экономию: вынесли общую секцию чата и убрали 263 строки чистого дубликата, добавив всего 46. Плюс по мелочи — заменили браузерные alert на нормальные тосты в восьми файлах, добавили первое сообщение от AI в чатах, поправили обращение в одном из модулей. Семь коммитов унификации за вечер, и всё в проде.

Цифры этого дня

  • 7 шагов процесса за 7 часов, с 06:06 до 13:00
  • 118 backend-тестов зелёные
  • 14563 города из GeoNames вместо 99 захардкоженных для тестов
  • 16 классов расхождений в UI между четырьмя модулями
  • Фаза 0: 8 багов в 10 файлах
  • Фаза 1: 6 тонких утилит, баланс +342/−332 строки
  • Фаза 2: убрали 263 строки дубликата чата, добавив 46
  • 8 экспертов в консилиуме (4 Claude + 4 GPT), 7 коммитов унификации
  • `pl-13` — класс, которого в Tailwind не существует, и из-за него текст во всех четырёх модулях шёл без отступа

Что из этого можно унести себе

Первое и самое практичное. Тех.долг в UI вы создаёте себе сами, тихо и постепенно, через копипасту с «мелкими правками». Никакой злой воли, просто каждый новый экран наследует от предыдущего, и дрейф копится, пока в один день вы не положите их рядом и не обомлеете. И лечится это не героическим переписыванием всего на свете, а вовремя пойманным «стоп, у меня уже три копии одного и того же, пора выносить в общее».

Второе. Когда вы строите MVP через AI-агентов, агент будет периодически переписывать scope под себя — помечать обязательное как «потом сделаем», и не из вредности, а просто потому что так быстрее закрыть. Ваша работа тут — держать в голове, зачем всё это, и ловить такие отмазки. Спека с чёткими критериями готовности — это не бюрократия, а единственное, что мешает фиче незаметно усохнуть.

И третье, главное. Не доверяйте одной модели проверять саму себя. Я гоняю аудит двумя разными ИИ ровно потому, что уверенность одного ничего не стоит, пока её не оспорил другой. Codex нашёл несуществующий класс, который мой основной агент не видел в упор, и наоборот так тоже бывает. Если вы выбираете между claude code или codex — да не выбирайте вообще, берите обоих и сталкивайте лбами. Два ИИ, которые спорят, поймают больше, чем один, который кивает. Вот и делайте выводы.