Три правки после демо Этапа 1 (все по TDD):
1. Карточка показывает ВСЕ данные поиска из payload (тип ProspectPayload 1:1 с
dataclass Firm; computed infoRows рендерит юрлицо, директора+личный ИНН,
контакты, бюджет Директа вилкой, каналы/коллтрекинг, оценку). Демо-сидер
кладёт полный синтетический payload.
2. Начальнику отдаётся manager_counts; фильтр показывает «Имя (N)».
3. Колонка «Переговоры» сортируется по next_call_at ASC (просроченные сверху).
Гейты: бэк 12/12, фронт 19/19, Larastan 0. НЕ выкачено (ждём разрешения владельца).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 14. /sales/prospects (менеджер), /sales/prospects/board
(начальник, salesBossOnly). Пункты «Потенциальные клиенты» и «Воронка отдела».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 12–13. SalesProspectsView (менеджер, listProspects свои),
SalesProspectsBoardView (начальник, listSalesManagers фильтр, все карточки).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 11. Без drag: карточки раскладываются по стадиям, клик эмитит
open, просроченный next_call_at подсвечивается.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 10. Селектор результата, поля по стадии, отказ скрыт при user,
обязательные причина/время. Тесты через vm (v-dialog телепортит контент).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 9. PROSPECT_STAGES (8 колонок), stageMeta, isOverdue;
listProspects/updateProspect в api/sales.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В боковом меню кабинета отдела продаж (блок «Начальник», только role=head)
добавлена внешняя ссылка на Python-поиск клиентов (2ГИС), развёрнутый отдельной
службой под lk.liderra.ru/finder/. Открывается в новой вкладке.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ветка разошлась с боевым 28.06 (334 коммита в main / 215 у нас). Влито ВСЁ боевое:
автоподбор конкурентов, мобильный адаптив портала, свой учёт посетителей, мониторинг
внешних сервисов, фиксы поставщика/биллинга/бота/разборов.
ПОБАЙТОВАЯ СВЕРКА: из 1008 файлов, изменённых боевым, 989 совпадают точно;
19 отличаются — все с нашей законной работой (обе стороны внутри). Затёртых — 0.
24 конфликта разобраны вручную. Ключевое:
- VerifySupplierOrderJob — взята БОЕВАЯ версия (фикс инцидента 11-12.07: площадка
берётся из src, а не из имени; наша была старой и вернула бы баг, терявший заявки).
- SyncSupplierProjectsJobTest — 15 боевых тестов + наш уникальный (limit-1 → только B1).
- routes/web, router/index, config/services, bootstrap/app — обе стороны сложены.
- NewProjectDialog — зелёные дни недели (наше) + мобильная раскладка (боевое).
- CHANGELOG схемы — номера версий столкнулись, наши перенумерованы в v8.67-v8.70.
- composer — обе зависимости (laravel-dompdf наш + geoip2 боевой).
Гейты: бэкенд 2907/2911 (0 падений), Larastan 0, фронт 1333/1333, сборка OK.
Baseline статанализа принял пре-существующий долг боевого кода (автоподбор/чат).
@mixin в 26 моделях — требование статанализа, dev-докблок, на рантайм не влияет.
Откат: git reset --hard pre-merge-main-20260714
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Разбор живого клиента (стоматология, Красноярск, 14.07): 2 часа настраивал 26
проектов, получил 14 отказов в трёх формах и ушёл, не заплатив.
Деньги:
- отменённый шлюзом платёж больше не висит «ожидает» вечно: закрываем как failed
с причиной (PaymentSettlementService — общий путь для webhook и крона);
- billing:reconcile-payments каждые 5 минут сам спрашивает шлюз про зависшие
pending. Побочно страхует от ПОТЕРИ ДЕНЕГ: если webhook не дойдёт, оплаченный
платёж всё равно зачислится;
- кабинет говорит правду: «Оплата не завершена» + «Оплатить снова» вместо
«баланс обновится автоматически» (GET /api/billing/last-payment).
🔴 RLS-мина (поймана валидатором ДО выката): UPDATE при отмене шёл без
tenant-контекста → на проде тронул бы 0 строк, а портал рапортовал бы «отменено».
Тесты слепы (тестовая БД под postgres). Регресс-тест проверяет ПОРЯДОК:
SET LOCAL tenant ДО UPDATE. Тот же класс, что инциденты 07.07 и 12.07.
Формы (клиент бился и уходил):
- удаление проекта со сделками: причина показывается на месте + кнопка
«Поставить на паузу» (раньше 422 улетал в никуда — 4 попытки впустую);
- создание проекта: ошибка по дням недели больше не молчит (у поля не было
места для показа — 2 немых отказа);
- автоподбор «Добавить вручную»: показываем причину от сервера (был голый
catch {}), длинные ссылки 2ГИС/Яндекс.Карт принимаются — трекинг-хвост срезаем
сами. Воспроизведено тестом: именно длинная ссылка давала 3 отказа подряд.
Наблюдаемость: причины отказов пишутся в журнал (маршрут, tenant, ИМЕНА полей;
значений нет — 152-ФЗ). Уровень warning: на проде LOG_LEVEL=warning, info в
журнал не попадает вовсе. Робот-сверщик добавлен в реестр пульса.
Тесты: Pest 2475/2475, Vitest 1215/1215.
Выкачено на боевой 14.07.2026 ~13:00 МСК; сверка сразу закрыла 3 мёртвых платежа
(10 000 ₽, 5 000 ₽, 1 000 ₽).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В «Отчётах» клиенту показывали «retry-failed для owner отчёта», «3 попыток
retry» и «Готовится асинхронно». В «Конкурентном поле» подсказка звала
элементы «подкомпаниями / ЖК» (чужая ниша). Переписано по-человечески:
перезапустить до 3 раз за 7 дней, готовится в фоне, элементы — филиалы или
отдельные бренды.
Тест на баннер отчётов теперь запрещает слово «retry» в интерфейсе.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
«Объединено в Белый Зуб14.07.2026» — между названием и датой не было разделителя.
Подсказка поля «Название элемента» звала пример из чужой ниши («ЖК Нанжуль-
Солнечный») — заменена на нейтральную.
Демо-стенд: лента «Активность» в карточке заявки была пустой (событий у демо-заявок
не было вовсе), журнал кошелька копил кривую хронологию прошлых прогонов — списания
стояли раньше пополнений. Журнал денег защищён от подделки (только дописывать), поэтому
сброс демо-журнала делается ЯВНО и только на локальной машине.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Два бага, которых не видел ни один тест — их нашёл робот, снимавший инструкции
для гостей.
1) Карточка остановленного проекта показывала одновременно «На паузе» и зелёную
плашку «Собирает заявки»: плашка смотрела только на связь с поставщиком (там
всё «ок») и про паузу не знала. Теперь серым: «Остановлен вами — заявки не
приходят».
2) В «Канбане» сумма в шапке колонки всегда была прочерком: считалась по старому
демо-полю, которого у живых заявок нет (там ноль), хотя на карточках стояла
честная цена. Теперь считает по настоящему списанию.
Тесты: сначала падающие, потом фикс. Весь фронт 1229 зелёных.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Воронка подсвечивает красным шаг, где теряем больше всего людей. Гости с VPN
и хостингов показаны отдельным числом, а не подмешаны в конверсию. Город при
VPN честно помечается как недостоверный.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ключ разговора в localStorage теперь привязан к тому, кто вошёл:
'liderra_chat_id_u<id>' в кабинете, 'liderra_chat_id_guest' на лендинге.
Смена вошедшего (вход/выход/вход под другим) очищает переписку на экране
и заводит новый ключ — иначе следующий человек за тем же компьютером
видел бы баланс и проекты прошлого клиента (окошко долбилось бы в чужой
разговор и получало 404 от серверной проверки владельца).
Config: jivo_bot -> bot (убраны неиспользуемые webhook_secret/outbound_url/token
- транспорт Jivo уже удалён), jivosite убран целиком (виджет больше не нужен).
Env: JIVO_BOT_* -> BOT_*; JIVO_WIDGET_ID/VITE_JIVO_WIDGET_ID удалены.
tours_enabled выключен по умолчанию (BOT_TOURS_ENABLED=false) — экскурсии
«Показать на портале» уходили не по теме вопроса (живая проверка 13.07.2026),
чинить релевантность отдельно; сторож-тест не даёт включить незаметно.
Заодно убраны обнаруженные хвосты того же виджета, оставшиеся от прежних
задач: JivoLivenessProbe (класс+тест+регистрация в мониторинге внешних
сервисов), плитка «JivoSite» в админ-дашборде, встроенный скрипт виджета
в welcome.blade.php, осиротевший тест resources/js/.../JivoWidget.vue
(компонент уже был удалён ранее), declaration VITE_JIVO_WIDGET_ID.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Вопрос владельца: «а цены в нём надо актуализировать руками или он сам тянет?»
Ответ был неприятный: руками. Цифры (300/50 руб, тарифные ступени) стояли текстом
в статьях — поменяли бы цену в админке, бот продолжил бы врать про деньги.
Теперь в статьях стоят метки {{цена_подбора}}, {{цена_изучения}}, {{тарифы}},
а сервис LivePrices подставляет живые значения из system_settings и pricing_tiers
В МОМЕНТ ОТВЕТА — без переиндексации базы знаний. Нет цены в настройках — метка
не протекает клиенту, бот отсылает к экрану, где цена написана всегда.
Из текстов экскурсий цифры цен тоже убраны (фронт не знает БД): шаг подсвечивает
строку с ценой в самом окне — она всегда актуальная.
Проверено живьём: поменял цену в настройках на 450 — бот сразу назвал 450.
Pest 42/42 (3 новых теста), Vitest каталога 8/8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Владелец: «клиентам надо подсказывать — от того, как они сформируют поле,
зависит весь дальнейший поиск». Плюс просьба вычитать инструкцию и экскурсии
на враньё и ловушки.
Добавлено предупреждение (и в экскурсии collect-competitors/proposals, и в статьи):
перед переносом конкурента в поле открыть сайт и справочник ссылками — тот ли это
бизнес, тот ли город, живой ли адрес. По грязной карточке мы найдём не те источники,
а изучение каждого конкурента стоит 50 руб.
Ревизия (агент сверил 36 статей и 31 экскурсию с кодом) — закрыто:
1. Вебхук обещал автоматическую отправку событий, которой НЕТ. Теперь честно:
сейчас только тестовый запрос; заявки забирать по API-ключу или выгрузкой.
2. Колонка «Город»: оговорка, что в редком запасном случае там регион проекта.
3. Отчёты: фильтры «Проект»/«Менеджер» пока только «Все» — сказано прямо.
4. Цены автоподбора: «актуальная цена всегда в самом окне».
5. Статьи «Портал на телефоне» и «Откуда заявки» больше не ведут на чужую
экскурсию про сделки (у них своей нет — кнопки просто не будет).
6. Синонимы: «портал на телефоне» и «заявки в свою CRM» находили не то.
Pest 39/39, Vitest 1227/1227. Живой прогон вопросов — отвечает верно.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Владелец застрял на шаге «Цена» (экскурсия про сбор конкурентов): карточка встала
ниже видимой части окна и вдобавок была накрыта виджетом Jivo — кнопку «Далее»
нажать было нечем, экскурсия превращалась в тупик.
1. Позиция карточки прижимается к экрану (clampTop): она всегда целиком видна,
какой бы низкой ни была цель шага.
2. z-index экскурсии поднят выше Jivo (виджет рисует себя поверх всего).
Оба бага сначала воспроизведены тестами. Vitest 1227/1227. Живьём: все 4 шага
экскурсии collect-competitors — карточка в экране, «Далее» кликабельна.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Владелец нажал «Показать на портале» в чате и не смог посмотреть: его выкинуло
на «Вход», после входа увело на дашборд (адрес экскурсии потерян), а сверху сел
приветственный тур «Добро пожаловать в Лидерру».
1. LoginView теперь возвращает клиента по redirect (роутер его уже сохранял) —
/dashboard?tour=dupes открывается сразу после входа. Чужой адрес в redirect
игнорируется (защита от подмены ссылки).
2. WelcomeTour молчит, если в адресе есть ?tour= — экскурсия из чата важнее.
Оба бага сначала воспроизведены тестами. Vitest 1225/1225. Проверено живьём:
гость → ссылка бота → форма входа → сразу «Шаг 1 из 4» нужной экскурсии.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(bot): всеобъемлющая инструкция (36 статей) + 31 узкая экскурсия, которая открывает окна
Живой урок владельца 12.07.2026: «дубли чистятся в 2-х местах, а бот выдумал третье»,
«стыдно такого бота показывать», «видео показываем полное, а я спрашиваю про одно —
надо резать», «надо не только кнопку показывать, а и что за ней».
База знаний: 17 обзорных статей → 36 узких по темам. Портал обойдён целиком
(5 агентов): все экраны, окна, подсказки «(?)», тексты ошибок, внутренние правила.
Новое, чего бот не знал: дубли в ДВУХ местах (Поле и Предложения) + окно «источник
уже есть»; правило 18:00; почему приходит меньше лимита; оплата по счёту и акты;
списания; безопасность и 2FA; отчёты; массовые действия; регистрация.
Экскурсии: 15 → 31, каждая на свою тему (3–7 шагов). Раннер GuidedTour научен
шагу `open` — сам открывает окно/вкладку и подсвечивает то, что ВНУТРИ
(окно дублей, форму подбора, диалог пополнения, панель тарифов, карточку сделки).
99 новых якорей data-tour расставлено по всему порталу (4 агента).
Поиск: веса в tsvector (заголовок A / синонимы B / текст C) — иначе на «чистка
дублей» первой всплывала статья «Списания» из-за мимоходного упоминания «поля».
Тесты: Pest бот 39/39, Vitest 1222/1222. Новые сторожа: у каждой статьи есть
экскурсия; экскурсия не короче 3 и не длиннее 7 шагов; каждая цель экскурсии
реально существует в разметке; экскурсии про действия обязаны открывать окно.
Живая проверка в браузере: экскурсия про дубли сама открыла окно объединения
и перешла на второе место чистки.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@
Претензия владельца 12.07.2026: «видео мало и они обрубленные».
Причина «обрубленности» найдена живым прогоном: раннер НЕ переключал экраны —
шаг про «Проекты» показывался поверх «Биллинга». Фикс GuidedTour: при переходе
на шаг с другим route зовём router.push (TDD-тест «шаг на другом экране»).
Каталог: 6 → 15 экскурсий, все минимум по 3 шага (тест-сторож в TourCatalog.spec):
start (4 шага), create-project (6), top-up-balance (6), tariffs (4), balance-pause (4),
deals (4), kanban (3), konkurentnoe-pole (6), change-source (4), security (4),
notifications (3), import (4), api (3), dashboard (4), support (3).
Якоря data-tour расставлены по портале (3 агента): биллинг (кошелёк, запас,
ступени, вкладки, сумма, способ оплаты), проекты (фильтры, карточка, меню, тип
источника, лимит, регионы, дни), сделки (фильтры, таблица, экспорт), канбан,
импорт, дашборд, настройки (вкладки, 2FA, матрица уведомлений), Поле (собрать,
вручную, дубли, вкладки, карточка, легенда, изучение), помощь (FAQ, форма).
Каждая из 17 статей инструкции получила свою экскурсию (frontmatter tour:).
Vitest 1216/1216, Pest бот 46/46, живая проверка в браузере: экскурсия сама
переходит между экранами и подсвечивает цели.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Широкая таблица (7 колонок) не влезала в узкую колонку планшета рядом с
rail-меню — правые колонки «Комментарий»/«Поставлен» обрезались на 600–1279px.
ResponsiveTable теперь показывает карточки на телефоне И планшете, таблицу —
только на десктопе (>=1280). Тесты табличного слоя зафиксированы на десктоп-ширину.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Имя конкурента хранится как «Бренд, описание» — в колонке «Конкурент»
показываем часть до первой запятой, полное имя — в title при наведении.
Применено к десктопной таблице и мобильной карточке. +тест на обрезку.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cherry-pick a7a4e735 поверх боевой ветки feat/portal-mobile-adaptive для
выката ТОЛЬКО правки тарифов на прод. Логика денег не менялась.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Админка показывала сырую ширину ступени (leads_in_tier: 250/500/1000...),
а кабинет клиента — накопительный диапазон (1-250, 251-750...). Одни и те же
данные, но выглядят как разные тарифы; в форме редактирования подпись
'Лидов в ступени' читается как потолок, а система понимает её как ширину -
ловушка при смене цен.
Логика денег не менялась. Изменён только показ в админке:
- колонка 'Диапазон у клиента' в активной сетке;
- живой предпросмотр диапазона в форме редактирования;
- расчёт диапазона вынесен в общий utils/pricingTiers.ts, чтобы админка
и кабинет больше не расходились.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Влитый из автоподбора deep-link «Сделки → Автоподбор» читал route.query
в onMounted без защиты. В приложении роутер всегда есть, но при монтировании
в юнит-тесте без роутера route === undefined → unhandled error (2 ошибки,
vitest exit 1). Добавил route?.query ?? {} — поведение в бою не меняется,
полный прогон снова зелёный (1184 ✅, exit 0). Правка нужна для чистого
CI-гейта выката мобильной ветки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
После вливания переделки сделок (колонки Конкурент/Источник со ссылками
в автоподбор) эти поля были только в таблице на компьютере — в карточке
телефона их не было. Добавил в мобильную карточку:
- «Конкурент» — имя конкурента зелёной ссылкой, ведёт в автоподбор
(/autopodbor?competitor=ID), рядом город;
- «Источник» — отдельной строкой домен/идентификатор ссылкой в автоподбор
(+ «· +N» если источников несколько) и канал (Сайт/Звонки/СМС).
Для сделок без источника автоподбора карточка выглядит как раньше
(проект · канал · город). @click.stop — ссылки не открывают деталь сделки.
Проверено на 390px: конкурент+источник видны, вбок не едет, переход
по ссылке в автоподбор работает; тесты DealsTable/mapper/ResponsiveTable
зелёные (23).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Прошёл все экраны портала на 390px, нашёл и починил переполнения:
1. NewProjectDialog: кнопки дней недели (ПН…ВС) в окне редактирования
проекта не влезали — СБ и ВС уезжали за правый край и были не видны.
На телефоне делим ряд поровну между 7 кнопками.
2. DaysBulkDialog (массовое «Дни сбора»): та же беда с двумя рядами дней —
тот же приём (flex 1/1/0 на телефоне).
3. ApiTab: пары кнопок «Копировать+Перегенерировать» и
«Сохранить+Тестовый webhook» не помещались в строку — длинная вторая
кнопка обрезалась. Разрешаем перенос на телефоне (flex-wrap).
4. NotificationsTab: таблица «События × каналы» была шире экрана —
колонка Email уезжала, а названия событий крошились по одному слову.
На телефоне ужимаем колонки-галочки и уменьшаем отступы, событию
достаётся почти вся ширина; заголовки каналов мельче.
Все правки — только в phone-media (<600px), десктоп не затронут.
Проверено замерами DOM + скриншотами на 390px; тесты затронутых
компонентов зелёные.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Раздел «5. Хранение и защита»: упоминание записи сессий Вебвизором с маскировкой
персональных данных контактов, обработка на серверах в РФ.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
router.afterEach: при входе на layout=app лениво грузит Метрику и шлёт hit(fullPath).
Вход/публичные/админка/портал продаж — без Метрики. + убраны лишние @ts-expect-error
в тесте (type-check чист).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
metrika.ts: читает id из <meta name=metrika-id>, один раз инжектит tag.js + init
webvisor:true (defer), hit(url) шлёт заход. Тесты tests/Frontend/metrika.spec.ts:
no-op без meta, идемпотентная загрузка, hit вызывает ym с id+url.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>