Обёртка ResponsiveTable (таблица на десктопе/планшете, карточки на телефоне
по ярусу useLayoutTier). Применена к реестру лидов «Сделки»: на телефоне —
карточки лида (чекбокс выбора, телефон, статус-плашка, источник·город,
комментарий, дата), тап открывает карточку сделки; на десктопе прежняя
v-data-table без изменений.
Тесты: ResponsiveTable.spec (2), «Сделки» 58/58 зелёные. Проверено на 390 и 1440px.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Регионы выводились в конституционном порядке (республики → края →
области), из-за чего список выглядел кашей (Дагестан → Донецкая
Народная → Ингушетия → Кабардино-Балкарская). Добавлен REGIONS_ALPHA:
сортировка по «настоящему» имени, слова-типы (Республика/область/край/
автономный округ) отбрасываются — Татарстан на «Т», Адыгея на «А».
Подставлен во все 7 выпадашек автоподбора. Тест regionsAlpha.spec.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Групповое и одиночное создание теперь ТРЕБУЮТ регион (vsyaRf=false по умолчанию;
«Вся РФ» — явная галочка, дефолта больше нет — подтверждено владельцем). Тесты
были под старый дефолт «Вся РФ» → doCreate бэйлился на гейте региона. Добавлен
явный выбор «Вся РФ» перед doCreate. Плюс requisites_required теперь открывает
форму реквизитов (reqGate), а не тост — тест переведён на новое поведение,
reqGate добавлен в defineExpose для наблюдаемости. Спек 6/6 зелёный.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Клик «Удалить» у предложения открывает окно со всеми предложениями,
найденными в том же месте (совпал url карточки/сайта в «где нашли»), с
галочками. По умолчанию отмечена только нажатая строка; клиент выбирает,
какие связанные строки удалить. «В работе» — прежнее окно. Массовое
удаление предложений — подтверждение со списком выбранных.
Только показ и порядок «сначала выбери, потом удали»; БД/бэкенд не тронуты.
Тесты: пул по url, строгое совпадение, дефолт-галочка, удаление отмеченных.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Разворот по телефонам 2ГИС и Яндекс больше не уходит цепочкой «до талого»:
по умолчанию берём только фирмы с ЛИЧНЫМ номером фирмы — глубина 1. Именно
неограниченная цепочка уводила в чужие фирмы, давая мусорные сайты в
предложениях холдинга. Многошаговый разворот сохранён за параметром maxDepth,
по умолчанию 1. Изолировано: работает только под флагом phone_reverse, основной
пул сбора не затрагивается. Бэкенд автоподбора 430 из 430.
Фронт: на вкладке «Предложения» добавлена кнопка массового удаления выбранных
источников — bulkDeleteProps через removeSource, покрыта тестами.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Слияние конкурентов больше не «чёрная дыра» (баг «САПС/Метрополис»: карточка
исчезла без следа, вернуть было нечем). Теперь каждое слияние пишет событие в
autopodbor_merge_events со СНИМКОМ поглощённых карточек (имя/сайт/телефоны/
справочники), кто и когда слил.
- Таблица autopodbor_merge_events (+RLS tenant_isolation, паттерн 1:1 с sources,
rls-ревью OK; nullOnDelete на survivor_id/user_id — история не рушится).
- AutopodborCompetitorMerger::merge($tenantId,$ids,$name,$userId) пишет событие.
- GET /field/merge-events — история; POST /field/merge-events/{id}/restore —
возврат поглощённых как предложения (защита от повторного возврата).
- Фронт: панель «🕘 История слияний» + «↩ Вернуть карточку».
Бэк 428/428, фронт 12/12, Pint чистый, build ок. schema.sql канон-синк всех
таблиц автоподбора — предвыкатный TODO (вся фича migration-only в этой ветке).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Баг «САПС/Метрополис»: клиент переименовал свежую карточку и слил — а выжила
ДРУГАЯ (с проектами/в поле), имя переименованной затёрлось вслепую, карточка
пропала. Теперь окно «Найти дубли» помечает карточку, которая ОСТАНЕТСЯ
(«· останется»), и берёт ЕЁ имя как имя объединённого по умолчанию — клиент
видит исход до подтверждения и не теряет карточку случайно.
Бэкенд (survivor-метка в duplicate-groups + AutopodborCompetitorMerger::survivorId)
уже в 853c5a00; здесь фронт: api-тип, окно, метка, тест. Фронт 11/11.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кнопка «Найти и объединить дубли» на экране поля теперь ловит и предложения,
дублирующие карточки поля (scope=cross): счётчик учитывает их, окно помечает
«· предложение», объединение сливает предложение в карточку поля (выживает поле,
merger не трогали). Бэкенд оставляет только группы с ≥1 карточкой поля — пары
чисто-предложений на экране поля не мусорят.
Файл-статус сессии — docs/superpowers/findings/2026-07-05-avtopodbor-session-status.md.
Feature 154/154, фронт 32/32, Pint чистый, билд собран.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Разворот по телефонам запускается ТОЛЬКО при наличии своего сайта (seedDomain).
Без сайта имя размыто (напр. «Южный берег» → автосервисы/гостиницы), сеедные
телефоны грязные, а разворот их лишь умножает. Доказано вживую: ДСК/СМ-сити
(сайт есть) — чисто; «Южный берег» (нет) — 70% мусора.
2. Поиск дублей научился кросс-ящик поле↔предложения (scope=cross): предложение-
дубль карточки поля теперь всплывает (merge кросс-ящик уже умеет). В выдаче
виден box каждой фирмы. Без scope — прежнее поведение (один ящик).
3. На кнопке «Найти и объединить дубли» показывается число найденных групп-дублей
(считается на загрузке, обновляется после слияния/«это разные»).
Юнит 267/267, feature 153/153, фронт 15/15.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Инвариант «один проект = один источник»: при переносе проекта на новый источник
снимаем привязку со всех прочих источников — проект больше не может висеть в двух
конкурентах сразу (жёсткий баг с дублями). Плюс разбор ошибки в автоподборе
показывает конкретную причину бэкенда вместо общей фразы «не удалось».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Клиент ушёл со страницы во время сбора и вернулся — индикатор пропадал (жил только
на опросе в памяти браузера), казалось, что процесс исчез.
- competitor-эндпоинт отдаёт active_run (идущий по конкуренту сбор) — тест
- loadCompetitor подхватывает active_run в currentRun (не затирая итог завершённого)
- onMounted возобновляет опрос, если сбор ещё идёт → индикатор оживает, дожидаемся итога
- тесты: active_run (бэк), loadCompetitor подхват/сохранение, подхват опроса на экране
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Клиент жаловался: кружок «завис» и прогресс не видно, когда окно сбора закрыл.
- CollectingIndicator: собственный гарантированно крутящийся кружок (старый ld-spin
из @import в scoped не анимировался — известный баг Vue) + текст очередь/этап.
- Индикатор ставится прямо на экран (конкурент и «поле») — виден, даже если окно закрыли:
«вы в очереди №N» / «этап N из M» / «идёт сбор…».
- В окнах сбора заменил статичный кружок на этот индикатор.
- 4 теста компонента + тест экрана.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кнопка «🔄 Собрать источники ещё раз» вместо мёртвой «✓ Источники собраны».
Окно предупреждает, что повтор платный (даже если нового нет). Честный итог
после сбора (N новых / M ранее удалённых). Блок «ранее удалённые — вернуть?»
с кнопкой «Вернуть» (box archived→proposal).
- RunDto.result + collectResultMessage (чистый хелпер) + тесты
- store.restoreSource + тест
- компонент RefoundDeletedSources + тест
- FieldCompetitorScreen: кнопка-повтор, честный итог, блок «вернуть», текст окна
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Компонент RunCollectStatus: «вы в очереди №N» (пока прогон ждёт) или «этап N
из M: подпись» (пока идёт). Встроен в оба окна сбора (конкуренты/источники) и
экран загрузки ручного изучения. RunDto получил queue_position и progress.
Никаких фейковых процентов — только реальные этапы движка. 4 теста Vitest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Диалог дублей: селект имени среди выбранных к слиянию, по умолчанию первое.
Merger и ручка принимают опциональное имя. Обе экрана.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В группе из 3+ клиент отмечает, кого объединить. По умолчанию выбраны все
(группа из 2 - как раньше). Слить можно любое подмножество >=2. На обоих экранах.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Бэк: хелпер CompetitorElements нормализует элементы и выводит легаси site_url/directory_urls
из ВСЕХ карточек. Ручки manual/update принимают elements.
Фронт: компонент CompetitorElementsEditor встроен в формы создания и правки на экранах
Поле и Предложения. Прежний баг - пересбор directory_urls из двух полей - закрыт.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- стор removeSource чистит store.sources - карточка конкурента обновляется без reload
- 8 оверлеев на экранах Поле/Предложения переведены на useOverlayDismiss
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FieldCompetitorScreen:
1) метки ✓ настоящий номер / 🎭 рекламный подменный + плейн-язык «где нашли»
+ легенда «как читать» (данные в БД и сверка с ЭТАЛОНом НЕ тронуты — перевод только в показе);
2) регион «впечатывается» — свой тайп-ахед в стиле экрана (без Vuetify, чтобы не ломать тесты)
в окнах «Создать проект» и «Настройки проекта»; чипы для мультивыбора, подпись округа;
3) галка «Вся РФ (все регионы)» даёт баннер-объявление;
4) групповое создание проектов из всех выбранных источников без проекта (source_ids пачкой),
заголовок/текст под «несколько». +тест на групповое создание. Фронт-спеки 24/24 зелёные.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Шаг 1 «Конкурентное поле» — чекпоинт:
- Финальный проход «Найти и объединить дубли» на поле и предложениях; клиент решает по каждой группе, ничего не склеиваем молча.
- Тихая склейка только по сайту и коду справочника; телефон и людный номер у более чем 4 фирм — на решение клиента.
- Колонка phones jsonb; телефон-дубли видны уже на «Предложениях».
- Sonar даёт только имя и тип, сайт всегда через EXA; вскрываем все карточки без гейта «есть сайт».
- Яндекс-карточки через локальный Playwright параллельно; рубрика из заголовка идёт в описание.
- Фикс рекламных номеров: 2ГИС-парсер разбирает телефоны пообъектно и выкидывает рекламные кнопки с platforms/caption чужих фирм.
Тесты: автоподбор 224/224 unit, 105/105 feature; фронт-спеки зелёные. Времянки app/scripts/diag-*/probe-* в гит не входят.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Фронт для правила «баланс блокирует запуск, а не создание»:
- ProjectLimitOverloadDialog переформулирован в «Проект не запущен — пополните
~X ₽ или уменьшите объём» (сумма в рублях topup_rub).
- NewProjectDialog: создание всегда успешно; при launch.deferred показывает
сообщение (не блок). Update-лимита 409 → тот же диалог.
- Возобновление (toggle-active) ловит 409 balance_insufficient (стор не
переключает is_active), показывает диалог; постоянная метка «Не запущен —
не хватает баланса» на карточке/в панели по preflight_blocked_at/balance_blocked.
- BulkActionsBar: сводка «Запущено N, отложено M — не хватает баланса» на resume.
- Автоподбор: CreateScreen показывает сводку запуска (рубли); FieldCompetitor
массовые pause/resume через общий POST /api/projects/bulk (наследует BULK_MAX,
слепок-защиту, балансовый гейт).
- ProjectResource отдаёт preflight_blocked_at.
Новые/обновлённые фронт-тесты зелёные. Пред-существующий красный baseline
фронт-набора (ErrorView/Settings/Legal/… + ProjectsView v-show) не трогался.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Баланс/статус различаем по типу сервиса (LIVENESS_ONLY_KEYS), не по null-балансу
(денежный Поставщик с сорванным автологином больше не показывается «выключено»).
Мини-плитка переименована «Балансы сервисов»→«Внешние сервисы».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Секция «На актуализацию»: дельта-карточка показывает текущие сайты и карточки фирмы поля + новый сайт кликабельными ссылками, кнопки Добавить и Заменить ведут через подтверждение «Создать проект?» в существующую форму CreateScreen с подставленным новым источником. Имя фирмы берётся из поля, не перезатирается.
Бэкенд: endpoint POST /competitors/{id}/actualize action=add|replace new_site. add заводит сайт-источник рядом со старым, replace архивирует старые сайт-источники и заводит новый, при активном проекте по старому сайту возвращает 409 manage_via_project. /proposals обогащён полем matched с текущими сайтами и карточками совпавшей фирмы.
По TDD. Бэкенд автоподбора 254/254, фронт автоподбора 64/64, ESLint чисто, Pint чисто.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stage 1: опознавалка фирмы по сайту/карточкам, ProposalClassifier new/actualize/archived/hidden, box=archived, bulk удалить всех ранее удалённых. Backend 249/249, front autopodbor 61/61.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Экран FieldCompetitorScreen:
- «где нашли» теперь кликабельные ссылки на источники (where_found из DTO),
адреса филиалов 2ГИС видны и кликабельны под номером;
- сортировка источников: больше подтверждений — выше, подменные — вниз;
- строка адреса офиса, счётчик подтверждений.
DTO SourceDto расширен полями where_found/confirmations/office (опциональны —
обратная совместимость: без них падаем на старое provenance_label).
Histoire-стори с живыми данными КрасЛомбарда (рендер настоящего компонента).
TDD: +2 теста, autopodbor-экраны 24/24. Бэкенд-провод where_found — отдельно (Plan C).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Сверка прототипа с реализацией показала расхождения — закрыты по TDD (dev, фронт):
- F1: экран «Предложения» (FieldProposalsScreen) переписан под вид «Поля» —
карточки-плитки field-shared, тип+«предложение», крупная похожесть, Сайт +
Справочник 2ГИС·Яндекс, править/удалять в карточке, массовый перенос; кнопка
«Собрать конкурентов» открывает единое окно сбора 300 ₽ вместо старого autoform.
- F2: новый дружелюбный админ-экран AdminAutopodborPricingView (правка цен
доп.услуг через PUT /api/admin/system-settings/{key} с обоснованием для аудита,
сетка лидов для справки) + маршрут /admin/autopodbor-pricing + пункт меню.
- F3: колонка «когда списывается» в панели доп.услуг биллинга.
- M2: удалён мёртвый экран FieldManualCompetitorScreen (+ спека) — на него не
было переходов; ручное добавление живёт окном на «Поле».
Тесты автоподбор+админ 43/43 зелёные, продакшен-вёрстка eslint-чистая, vite build ✅.
НЕ на проде. M1 (18:00/21:00 МСК) — не баг, реальный инвариант продукта, не трогал.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Фича «Конкурентное поле» на dev до уровня прототипа 2026-06-29-konkurentnoe-pole-proto.html.
Данные: box (proposal|field) на competitors+sources; phone_type city/mobile/tollfree рядом
с phone_kind (вариант C). 3 миграции, дефолты тарифов 300/50.
API (AutopodborController): GET /field (+счётчики), GET /proposals, PATCH/DELETE competitors
и sources с гвардами активного проекта, переключение box, POST /competitors/manual (+directory_urls),
competitor(id) обогащён box+project-статусом; projectStatus отдаёт limit/delivered/days/regions.
Смена источника проекта = PATCH /api/projects/{id} (реальный гвард слепка §14.10).
Фронт: FieldWorkspaceScreen/FieldCompetitorScreen/FieldProposalsScreen/FieldManualCompetitorScreen
+ field-shared.css (Forest) + AutopodborServicesPanel в Биллинге. Дословно по прототипу: подзаголовки,
баннер предложений, баннер правил времени 18:00 МСК, Справочник 2ГИС·Яндекс, статус проекта
5/день·заявки, окна сбора с ценами 300/50 + «что известно», полные формы. Пункт меню «Конкурентное поле».
Тесты: backend автоподбор 80/80, фронт автоподбор 49/49. Движок шага 2 = заглушка FakeCompetitorAgent.
OmegaDemoFieldSeeder — только для визуальной проверки (НЕ на прод).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AdminTenantsView грузил всех тенантов разом и фильтровал в браузере — на 1000
клиентов поиск/чипы видели только первую страницу. Теперь страница из limit/offset
+ v-pagination; поиск (ILIKE), статус (производный trial/overdue/active/suspended)
и тариф — серверные multi-фильтры. AdminTenantsController::index: statuses/tariffs
через CASE/whereIn (статус зеркалит adminTenantsMapper.deriveStatus). Опции тарифов —
отдельным запросом listAdminTariffPlans. Демо локально подтверждено.
Тесты: фронт 34/34 (tenants), бэкенд 13/13 (+2 на statuses/tariffs); baseline getJson 13→15.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фундамент под сквозную вложенность: periodRange() читает date_from/date_to
(приоритет) либо preset; Финансы и Клиенты считаются по выбранному периоду через
whereBetween. FE: «Свой период» + два date-поля + «Применить» → date_from/date_to.
Спека дизайна A+B+C+масштаб сохранена. Baseline перегенерирован (getJson тестов).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дашборд не было видно в сайдбаре — уйдя с него, нельзя было быстро вернуться.
Добавлен первый nav-пункт «Командный центр» → /admin/dashboard (иконка dashboard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6-я плитка «👥 Клиенты» со светофором (amber если есть спящие) + drill:
KPI за период (всего активных / новых / заходили / получали лиды / платили),
список новых клиентов (с датой входа/лидами/балансом) и «спящих» (активные
без входа 14+ дней или ни разу = не активировались). Клик по строке → карточка
клиента. Backend: clients() endpoint + clientsTile в summary (cross-tenant через
pgsql_admin); сигналы — users.last_login_at, deals, balance_transactions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
По сверке прод-данных с реальностью (часть чисел вводила в заблуждение):
- Финансы: +периоды 60 и 90 дней (крупные пополнения старше 30д теперь видны).
- Здоровье: «инциденты» больше не считают авто-лог ошибок джоб (summary
'Автоматически:%') — раньше копилось 975 и держало красный ложно. Теперь:
open_incidents = только реальные; добавлен job_errors_24h (повторяющиеся
ошибки джоб за сутки) в подсистему queues.
- Лиды: убраны обманчивый «% доставки» (это было «обработано», не доставлено)
и «нераспределённые по менеджерам» (менеджеры не используются). Добавлено
«получено от поставщика сегодня»; доставлено = реально созданные сегодня сделки.
- Заказ: показаны дата снимка и полная картина (всего активных заказов /
Σ лимита у поставщика) — сверка по снимку больше не выглядит занижено.
Тесты: admin-срез 87 зелёных, unit 3/3, фронт 10/10. stan 0, pint/eslint/
type-check/build чисто.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>