docs(конкурентное поле): отчёт теста «тупого клиента» + план/спека + 7 скринов

Прогон фичи глазами «самого тупого клиента» на изолированной тест-БД (прод не тронут):
все формы, групповые действия, биллинг с реальным списанием, эмуляция поставки Агента, визуал.
Деньги в порядке (300/50 списываются ровно при успехе, без двойных/ложных списаний).
Найдено и ПОЧИНЕНО 6 находок (код — коммит worktree-avtopodbor 1b3683c6).
Независимая перепроверка по коду; одна самокоррекция (общий тост, не «полная тишина»).

эскейп: фиксируй (авторизовано владельцем)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-30 06:43:26 +03:00
parent c632033809
commit f5b03540e5
11 changed files with 588 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

@@ -0,0 +1,234 @@
# Тест «Конкурентного поля» глазами тупого клиента — НАХОДКИ (30.06.2026)
Стенд: воркстри `worktree-avtopodbor`, свежая изолированная БД `liderra_dumbtest`
(локальный dev-postgres 127.0.0.1, НЕ прод). Прод/gitea не тронуты. План —
[RUN](../plans/2026-06-30-konkurentnoe-pole-dumb-client-test-RUN.md), спека —
[design](../specs/2026-06-30-konkurentnoe-pole-dumb-client-test-design.md).
Приоритеты: 🔴 блокер · 🟠 спотыкание клиента · 🟡 косметика · 🟢 ок/плюс.
> ⚠️ **ОТКАТИТЬ В КОНЦЕ:** воркстри `app/.env` временно изменён для изоляции стенда —
> `DB_DATABASE=liderra_dumbtest` (было `liderra`), `QUEUE_CONNECTION=database` (было `redis`).
> Вернуть обратно: `DB_DATABASE=liderra`, `QUEUE_CONNECTION=redis`. Файл gitignored, не коммитится.
>
> **Методическая засада стенда (НЕ баг продукта):** на Windows `DB_DATABASE=… php artisan serve`
> НЕ пробрасывает env в дочерний веб-процесс (`$_ENV` пуст, `variables_order` без `E`), поэтому
> приложение читает БД из `.env`. Изоляцию тест-стенда задавать ТОЛЬКО через `.env`, не env-префиксом.
> Из-за этого первые попытки «Собрать» давали ложный `run_in_flight` 409 — били в dev-`liderra`
> (там омега=tenant 34 с зависшим queued-прогоном), а не в чистую `liderra_dumbtest`. Записей в
> dev-`liderra` тест не сделал (всё блокировалось 409).
---
## ✅ ПОЧИНЕНО 30.06 (по TDD, в воркстри `worktree-avtopodbor`, dev)
Все находки закрыты. Тесты: Pest автоподбор **82/82**, Vitest автоподбор+админка **62/62**,
`vite build` ✓, продакшен-код eslint-чист.
- **FIX-1 (GENERIC-ERROR)** — новый маппер `autopodborErrorMessage()` в `api/autopodbor.ts` (+5 тестов):
окна «Собрать конкурентов/источники» теперь показывают адресный текст по коду ответа
(`balance_insufficient` → «Не хватает денег — пополните», `run_in_flight` → «Подбор уже идёт»,
валидация → по полю). Подключён в `catch` обоих экранов.
- **FIX-2 (B-REGION)** — регион по умолчанию = пустой плейсхолдер «— выберите регион —»
(обязателен), вместо «Республики Адыгея».
- **FIX-3 (D-STUDIED)** — у изучённого конкурента кнопка «Собрать источники» заменена на
«✓ Источники собраны» (disabled). +2 теста.
- **FIX-4 (B-DUP)** — сквозной дедуп в `RunAutopodborSearchJob`: конкуренты, уже известные тенанту
(чужие прогоны/ручные), не дублируются; если нового ничего нет — прогон «пусто», без списания.
Идемпотентность ретрая своего прогона сохранена. +1 тест.
- **FIX-5 (J-ADMIN-ID)** — убран захардкоженный `admin_user_id:1` с фронта (поле опционально, id
админа проставит бэкенд из сессии). *Полный фикс ждёт saas-admin авторизацию (MVP-долг бэкенда).*
- **FIX-6 (F-MIG)** — в 3 миграции автоподбора добавлен идемпотентный гард `to_regclass` (как в
остальных миграциях проекта); `migrate:fresh` снова зелёный (проверено на свежей БД).
⚠️ **НЕ закоммичено** (жду «эскейп»). Перед прод-выкатом — отдельный предполётный хвост
(canon-sync schema.sql, rls-reviewer, larastan, ребейз). I3-MOBILE (сайдбар на узком экране) НЕ
трогал — это общий `AppLayout`, не экран фичи.
---
## ИТОГ ПО ПРИОРИТЕТУ (отранжировано по «больно клиенту»)
**Деньги — в порядке (проверено по базе).** Подбор списывает ровно 300₽ при успехе, изучение — 50₽;
при пустом/нехватке/повторе НЕ списывает, в минус не уходит, двойного списания нет. Ни одного
ложного/двойного списания за весь прогон.
**🟠 Что чинить (спотыкания тупого клиента):**
1. **GENERIC-ERROR** — окна «Собрать конкурентов/источники» на любую ошибку показывают один общий
краткий тост «Проверьте баланс» (или ничего на пустой форме). Для «уже идёт» и пустой формы —
вводит в заблуждение. Сделать адресные сообщения + подсветку полей. *Главный UX-пробел.*
2. **B-REGION** — регион по умолчанию «Республика Адыгея» (чужой). Поставить регион клиента/пустой
плейсхолдер, иначе платный подбор уйдёт по чужому региону.
3. **B1** — пустая форма: нет подсветки/подсказки, что заполнить.
4. **B8** — нехватка денег: нет явного «пополните баланс».
**🟡 Мелочи:**
5. **B-DUP** — повторные подборы плодят дубли карточек (нет сквозного дедупа предложений).
6. **D-STUDIED** — кнопка «Собрать источники» активна у уже изучённого конкурента и молча ничего не
делает.
7. **J-ADMIN-ID** — админ-экран тарифов шлёт захардкоженный `admin_user_id:1`.
8. **F-MIG-01** — 3 миграции автоподбора без идемпотентного гарда → ломается `migrate:fresh`
(предполётный долг, в код фичи попал из canon-sync).
9. **I3-MOBILE** — на узком экране сайдбар стопкой над контентом (общий AppLayout).
**🟢 Что хорошо:** экран «Поле»/«Карточка»/«Предложения»/«Админ-тарифы» — единый Forest-стиль и понятная
иерархия; типы телефонов (городской/мобильный/8-800) + коллтрекинг ✓/🎭 рендерятся идеально; платный
путь подбор→предложения работает с авто-переключением; админ-экран ошибку показывает правильно;
пустые/загрузочные состояния и баннеры сроков (18:00/21:00 МСК) на месте.
**Движок «под капотом» — заглушка** (`FakeCompetitorAgent`), на звене 4 усилена до реалистичной
поставки (конкуренты с разной похожестью + источники со всеми типами телефонов вкл. 8-800). Реальный
ИИ-движок — отдельная работа (замена binding в `AutopodborServiceProvider`).
---
## Находки
### 🟠 F-MIG-01 — fresh-migrate / migrate:fresh сломан (3 автоподбор-миграции без идемпотентного гарда)
**Где:** `database/migrations/2026_06_28_100000_create_autopodbor_runs.php``100100_*_competitors`,
`100200_*_sources`).
**Симптом:** на свежей БД `php artisan migrate` падает: `SQLSTATE[42P07] отношение "autopodbor_runs"
уже существует`.
**Корень:** проектный паттерн — `db/schema.sql` это squashed-база (грузится `0001_..._load_initial_schema`),
а каждая миграция, дублирующая таблицу из schema.sql, имеет гард
`if (to_regclass('public.X') !== null) return;` (см. `2026_05_19_..._create_supplier_manual_sync_queue`).
Canon-sync v8.58 добавил автоподбор-таблицы в schema.sql, но в 3 автоподбор-миграциях остался простой
`Schema::create(...)` БЕЗ гарда → загрузчик создаёт таблицу, миграция падает на дубле.
**Кого бьёт:** любой, кто поднимает чистый стенд или делает `migrate:fresh` (CI, новый разработчик,
приёмочный прогон). На старой инкрементальной БД (`liderra_testing`) не воспроизводится — там таблицы
миграций уже отмечены.
**Не из этой сессии:** предсуществующий долг канон-синка, не код фичи и не UX. Уже в предполётном
хвосте HANDOFF (canon-sync + rls-reviewer).
**Рекомендация:** обернуть `up()` трёх автоподбор-миграций тем же гардом `to_regclass`, что и
`supplier_manual_sync_queue` (3 строки на файл). Тогда `migrate:fresh` снова зелёный. Правка кода —
под «эскейп»/в предполёт.
**Обход для стенда (применён, кода не трогал):** после загрузчика зарегистрировал 3 миграции в таблице
`migrations` как выполненные → `migrate --force` домигрировал box/phone_type/цены; схема корректна
(box на обоих, phone_type на источниках, цены 300/50, pending=0).
---
### 🟢 A1/A3 — экран «Поле»: первый взгляд понятен
Заголовок, подзаголовок «Ваши конкуренты: 5 · проектов в работе: 1. Поле ведёте вы — Лидерра
подсказывает», две крупные кнопки, баннер-черновик «💡 Лидерра подготовила 5 в предложениях…
Разобрать предложения →», ящики «В поле 5 / Предложения 5», карточки с типом/похожестью/Справочником/
счётчиками, «✎ Изменить / ✕ Удалить / Открыть конкурента →». Forest-палитра. Клиенту ясно, что делать.
Скрин `DUMB-A-field-first-look.png`.
### 🟠 B1 — пустая форма «Собрать конкурентов»: кнопка молчит, нет подсказки
**Где:** окно «Собрать конкурентов для меня», кнопка «Запустить подбор (платно)».
**Что делает тупой клиент:** открыл окно, ничего не заполнил, нажал «Запустить подбор».
**Ожидал:** подсказку «заполните: чем вы занимаетесь, минимум 2 примера» / подсветку полей.
**Увидел:** окно осталось, без подсветки полей и без понятного указания, ЧТО заполнить (в живом тесте
сообщения на экране не увидел; возможен краткий общий тост «Проверьте баланс» — см. GENERIC-ERROR,
который для пустой формы вводит в заблуждение). Клиент не понимает, почему «не работает».
Скрин `DUMB-B1-empty-submit.png`.
**Деньги:** ✅ безопасно — списания нет (tx=0), прогон не создан (баланс цел). Проблема — нет
адресной валидации/подсветки полей.
**Рекомендация:** при клике на невалидной форме — подсветить первое незаполненное поле + короткий
текст «Заполните, чем вы занимаетесь» / «Нужно минимум 2 примера». Не оставлять клиента в тишине.
### 🟠 B-REGION — регион по умолчанию «Республика Адыгея» (чужой клиенту)
**Где:** окно «Собрать конкурентов», поле «Регион поиска *».
**Симптом:** по умолчанию выбрана **первая по алфавиту** «Республика Адыгея», а не регион клиента
(у Омеги — Красноярский край) и не пустой плейсхолдер «Выберите регион».
**Кого бьёт:** тупой клиент не заметит и запустит платный (300 ₽) подбор по чужому региону → деньги
за бесполезный результат.
**Рекомендация:** дефолт = регион тенанта (если известен) ИЛИ пустой плейсхолдер «— выберите регион —»,
который заставляет выбрать осознанно. То же проверить в окне «Создать проект» (E1).
### 🟢 B-happy / H1 — платный подбор работает, списание корректно
Заполнил форму (описание, регион Красноярский край, 2 примера) → «Запустить подбор (платно)» →
окно «Идёт подбор… Можно закрыть вкладку, деньги спишутся только при успехе» → воркер обработал за
83 мс → авто-переключение на «Предложения 13» с подзаголовком «Найдено в предложениях: 13. Это
черновик — разберите и перенесите нужных в поле». **Деньги:** баланс 49700→49400, ровно ОДНА
транзакция `autopodbor_charge 300.00` (H1 ✅). Карточки предложений — единый стиль с «Поле».
### 🟢 H3 — двойного списания нет (гвард run_in_flight)
Пока прогон `queued/running`, повторный «Собрать» того же типа получает 409 `run_in_flight`
второй платёж не проходит. Подтверждено и кодом (`assertNoInFlight`), и живым 409 в ходе теста.
### 🟡 B-DUP — повторные подборы плодят дубли карточек в «Предложениях»
После двух подборов в «Предложениях» висят пары-дубли («Окна Комфорт» ×2, «Пластика Окон» ×2,
«Фабрика Окон» ×2, «Балкон-Сервис 16» ×2). Дедуп работает ВНУТРИ прогона (ключ
`tenant+search_run_id+dedup_key`), но НЕ между прогонами → один и тот же конкурент из разных
подборов = разные строки. Тупой клиент, кликнувший «Собрать» дважды (например, не увидев результат
из-за async), получит задвоение. **Рекомендация:** дедуп предложений в пределах ящика «proposal»
по конкуренту, а не по прогону; при повторной находке — обновлять существующую карточку.
*Замечание:* усилено фикс-данными заглушки; реальный движок может отдавать иные наборы.
### 🟢 D3 / H2 — изучение конкурента: списание 50₽ + все типы телефонов
Бэкенд+очередь+воркер: study run → done, **ровно 50₽** (баланс 49400→49350, одна транзакция).
Источники из заглушки (после звена 4) включают полный набор: сайт ×2, телефоны
city ✓(настоящий), city 🎭(подменный/коллтрекинг), mobile, **tollfree 8-800**. На карточке
КрасЛомбарда вживую виден бейдж «✓ городской». Тип номера + метка коллтрекинга отображаются.
### 🟡 D-STUDIED — кнопка «Собрать источники» предлагается у уже изучённого конкурента и молчит
Открыл уже изучённого КрасЛомбарда → «✨ Собрать источники для меня» доступна. Клик → бэкенд
(`startStudy`: `studied_at != null → вернуть существующий прогон`) корректно НЕ списывает 50₽ и не
изучает повторно, но клиент не видит ни нового результата, ни сообщения «конкурент уже изучен».
**Деньги:** ✅ безопасно (повтор бесплатен). **Рекомендация:** для изучённого конкурента — менять
кнопку на «Источники собраны» (disabled) или показывать «Уже изучали N.NN — повторный сбор не нужен».
### 🟠 B8 / H4 — нехватка баланса: списания нет, но клиент НЕ видит «не хватает денег»
Поставил баланс 100 ₽, запустил подбор (300 ₽). **Деньги:** ✅ безопасно — баланс остался 100,
прогон не создан, в минус не ушло (H4 ✅). **НО:** ни сообщения «недостаточно средств», ни
«пополните баланс» в DOM на момент проверки не было (0 упоминаний скриптом). По коду срабатывает
краткий общий тост «Проверьте баланс» (см. GENERIC-ERROR) — но он авто-исчезает и не говорит явно
«пополните». Тупому клиенту неочевидно, что нужно пополнить. Скрин `DUMB-B8-insufficient.png`.
### 🟠 GENERIC-ERROR (сводная, УТОЧНЕНО верификацией) — общий тост «Проверьте баланс» на ВСЕ ошибки
⚠️ *Самокоррекция:* сначала записал как «полная тишина» — **верификация по коду опровергла**. На
самом деле `catch` в окнах есть и показывает тост (axios отклоняет 4xx → catch срабатывает):
`FieldWorkspaceScreen.vue:134-137``flash('Не удалось запустить подбор. Проверьте баланс.')`;
`FieldCompetitorScreen.vue:184-187``flash('Не удалось собрать источники. Проверьте баланс.')`.
**Проблема (реальная):** тост **хардкоженный и общий** — один и тот же «Проверьте баланс» для ВСЕХ
причин: нехватка денег (верно), `run_in_flight` «подбор уже идёт» (вводит в заблуждение), 422
пустая форма (вводит в заблуждение). НЕ через `extractErrorMessage` (админ-экран — через него, делает
правильно). Плюс тост краткий/авто-исчезает — в живом тесте B8 я его упустил по таймингу
(в DOM на момент проверки сообщения уже не было). **Рекомендация:** в `catch` парсить код ответа
через `extractErrorMessage`/по `error`: `balance_insufficient` → «Не хватает денег — пополните»;
`run_in_flight` → «Подбор уже идёт, дождитесь результата»; 422 → «Заполните описание + минимум 2
примера». Заодно — подсветка незаполненных полей при пустой форме (B1).
### Проверка находок (звено 9, независимый субагент-скептик по коду)
- **GENERIC-ERROR** — ПОДТВЕРЖДЕНО с уточнением (выше): тост есть, но хардкоженный/общий, не
error-specific; контраст с админкой (`extractErrorMessage`) подтверждён.
- **B-DUP** — ПОДТВЕРЖДЕНО: `RunAutopodborSearchJob.php` ключ `updateOrCreate` включает
`search_run_id` → конкурент из разных прогонов = разные строки.
- **D-STUDIED** — ПОДТВЕРЖДЕНО (сервер: `startStudy` возвращает прогон без списания при
`studied_at != null`) + фронт `FieldCompetitorScreen.vue` НЕ скрывает/не дизейблит кнопку «Собрать
источники» при `studied_at` (нет проверки) — кнопка всегда активна.
- **J-ADMIN-ID** / **F-MIG** — подтверждены прямым чтением кода (захардкожен `admin_user_id:1`;
3 миграции `2026_06_28_*` без идемпотентного гарда).
### 🟢 J — админ-экран «Тарифы «Конкурентного поля»» грузится и валидирует корректно
`/admin/autopodbor-pricing`: заголовок, поля «Сбор конкурентов 300» / «Сбор источников 50», поле
«Причина (мин. 30)» предзаполнено, кнопка «Сохранить тарифы» **заблокирована без изменений** и
**активируется** при правке. Справочная сетка лидов. Скрин `DUMB-J-admin-pricing.png`. В отличие от
клиентских окон, при ошибке сохранения экран **показывает** дружелюбное «Не удалось сохранить.
Проверьте данные…» (правильный паттерн — клиентским окнам не хватает именно этого).
### 🟠 J-ADMIN-ID — админ-экран шлёт захардкоженный `admin_user_id: 1`
Сохранение цены (350) вернуло 422: тело запроса `{value, reason, admin_user_id: 1}` — id админа
зашит константой `1`. В тест-БД saas-админа с id=1 нет → отказ. На проде сработает только если id=1
случайно валиден. **Рекомендация:** подставлять id реально залогиненного админа (из auth-стора),
а не константу. *NB:* сам отказ в тесте — частично из-за отсутствия админ-пользователя в свежей
тест-БД, но захардкоженный id — реальный долг экрана F2.
---
## Визуал (звено 6)
### 🟢 Палитра/типографика/иерархия — соответствуют Forest и прототипу
Все экраны (Поле, Карточка конкурента, Предложения, Админ-тарифы) — палитра Forest (teal #0F6E56,
ivory фон, тёмный сайдбар), карточки с тенью, крупная «похожесть %», бейджи тип/предложение/телефон,
понятная иерархия. Состояния проверены вживую: пустое («Предложений по источникам пока нет…»),
спиннер («Идёт подбор…»), модалки, бейджи ✓/🎭 + городской/мобильный/8-800. Скрины `DUMB-*.png`.
### 🟡 I3-MOBILE — на узком экране (390px) сайдбар стопкой над контентом
Карточки корректно перестраиваются в 1 колонку, всё читаемо. Но тёмный сайдбар на мобильном
рендерится стопкой НАД контентом (полное меню), хотя в шапке есть «гамбургер» ☰ — drawer
сворачивается не полностью. **Это общий `AppLayout`, не экран фичи** — на решение, но не блокер
(продукт десктоп-первый). Мелочь: «✎ Изменить / ✕ Удалить» переносятся на 2 строки на узком экране.
Скрин `DUMB-I3-mobile-field.png`.
@@ -0,0 +1,93 @@
# ТЕСТ «Конкурентного поля» глазами «самого тупого клиента» — хартия + промт
Документ-эстафета на случай /compact. Вверху — **готовый промт** для старта тестирования
(скопировать целиком в новую сессию). Ниже — согласованный план, цепочка скилов, рамки.
Связано: [HANDOFF фичи](2026-06-29-konkurentnoe-pole-HANDOFF.md) ·
[находки прототип↔реализация](../findings/2026-06-30-konkurentnoe-pole-proto-vs-impl-gap.md) ·
эталон-прототип `docs/superpowers/prototypes/2026-06-29-konkurentnoe-pole-proto.html`
(поднять: `node docs/superpowers/prototypes/serve.js` → http://localhost:8123).
---
## 📋 ГОТОВЫЙ ПРОМТ ДЛЯ СТАРТА ТЕСТИРОВАНИЯ (скопировать как есть)
```
Начинаем тестирование «Конкурентного поля» глазами «самого тупого клиента».
Сначала прочитай по порядку и ничего не упусти:
1. docs/superpowers/plans/2026-06-30-konkurentnoe-pole-DUMB-CLIENT-TEST-charter.md ← этот файл: план, цепочка скилов, рамки, чек-лист
2. docs/superpowers/findings/2026-06-30-konkurentnoe-pole-proto-vs-impl-gap.md ← что уже доделано (F1–F3, M2)
3. docs/superpowers/plans/2026-06-29-konkurentnoe-pole-HANDOFF.md ← состояние фичи, окружение, стенд
4. docs/superpowers/prototypes/2026-06-29-konkurentnoe-pole-proto.html ← визуал-эталон (localhost:8123)
ОБЯЗАТЕЛЬНО иди по согласованной ЦЕПОЧКЕ СКИЛОВ (звенья 1→10 в charter), КАЖДЫЙ скил
инвокируй через Skill-tool ПЕРВЫМ для своего звена — не пропускать, не «срезать»:
Звено 1 — skill superpowers:brainstorming (хартия теста + чек-лист, согласовать со мной)
Звено 2 — skill superpowers:writing-plans (пошаговый сценарий «ожидал→увидел»)
Звено 3 — поднять стенд (СВЕЖАЯ изолированная тест-БД, миграции, сид демо-Омеги, vite build, php artisan serve)
Звено 4 — skill superpowers:test-driven-development (усилить заглушку Агента до реалистичной поставки; Pest зелёный)
Звено 5 — Playwright MCP (прогон глазами как тупой клиент: пустые поля, мусор, двойные клики, Отмена, F5, назад)
Звено 6 — skill frontend-design + Playwright (ВИЗУАЛ: сверка с эталоном :8123 бок-о-бок, палитра/шрифты/отступы/иерархия, единство «Поле»↔«Предложения», пустые состояния/модалки/тосты/бейджи, адаптив resize в мобильный)
Звено 7 — Laravel Boost MCP database-query (ДЕНЬГИ: баланс ДО/ПОСЛЕ, транзакции, блокировки, «пусто→не списали»)
Звено 8 — skill superpowers:systematic-debugging (только если реально падает)
Звено 9 — субагент-верификатор + skill superpowers:verification (перепроверка находок, отсев ложных, ранжир по «больно клиенту»)
Звено 10 — отчёт: находки (UX + ВИЗУАЛ + деньги) + рекомендации по улучшению, по приоритету
Глубина — ПОЛНАЯ (формы + групповые + биллинг с реальным списанием 300/50 + эмуляция поставки Агента
+ сквозной путь лид→сделка). База — СВЕЖАЯ ИЗОЛИРОВАННАЯ тест-БД.
РАМКИ (жёстко): прод и gitea НЕ трогать; тест-БД и правки заглушки — ТОЛЬКО стенд; ничего не
коммитить без моего «эскейп». Говори простым русским, без жаргона. Я — «самый тупой клиент»:
любое место, где можно ошибиться/споткнуться/не понять — это находка.
Стартуй со звена 1 (brainstorming) — пришли мне чек-лист сценариев на согласование.
```
---
## ✅ СОГЛАСОВАНО (решения владельца)
- **Глубина:** ПОЛНАЯ — все формы + групповые действия + биллинг с реальным списанием (300 ₽ сбор
конкурентов / 50 ₽ сбор источников) + эмуляция поставки Агента + сквозной путь лид→сделка.
- **Тест-база:** СВЕЖАЯ ИЗОЛИРОВАННАЯ (отдельная чистая тест-БД, миграции + сид демо-Омеги). Прод ни при чём.
- **Персона:** «самый тупой клиент на свете» — нигде не должен ошибиться/споткнуться/не понять.
- **Визуал — обязателен** (отдельное звено 6): не только «работает руками», но и «красиво/понятно глазом, как проектировали».
## 🔗 ЦЕПОЧКА СКИЛОВ (по звеньям)
| # | Звено | Скил / инструмент | Что делает |
|---|---|---|---|
| 1 | Хартия теста | **skill `superpowers:brainstorming`** | Персона + полный чек-лист сценариев. Согласовать с владельцем. |
| 2 | План прогона | **skill `superpowers:writing-plans`** | Пошаговый сценарий «ожидал → увидел». |
| 3 | Стенд | **Bash/PowerShell** + **Laravel Boost MCP** | Свежая изолированная тест-БД, миграции, сид, `vite build`, `php artisan serve`. |
| 4 | Эмуляция Агента (dev-only) | **skill `superpowers:test-driven-development`** + **Edit** | Заглушка `FakeCompetitorAgent` → реалистичная поставка (конкуренты/источники/типы телефонов/лид); Pest остаётся зелёным. Каркас НЕ в прод/коммит. |
| 5 | Прогон глазами | **Playwright MCP** | Кликаю как тупой клиент: пустые поля, мусор, двойные клики, Отмена, F5, назад, платное дважды. |
| 6 | 🎨 Визуал и дизайн | **skill `frontend-design`** + **Playwright** (`browser_take_screenshot`, `browser_resize`) | Сверка с эталоном :8123 бок-о-бок: палитра Forest, шрифты, отступы, иерархия; единство «Поле»↔«Предложения»; пустые состояния/модалки/спиннеры/тосты/бейджи/bulkbar; адаптив (мобильный). |
| 7 | Деньги | **Laravel Boost MCP** (`database-query`) | Баланс ДО/ПОСЛЕ каждого платного действия, транзакции, блокировки, «пусто → не списали». |
| 8 | Корень бага (если есть) | **skill `superpowers:systematic-debugging`** | Докопаться до причины реального падения. |
| 9 | Независимая перепроверка | **субагент-верификатор** (`Explore`/general-purpose) + **skill `superpowers:verification`** | Скептик воспроизводит находки, отсеивает ложные, ранжирует по «больно клиенту». |
| 10 | Отчёт | я | Находки (UX + визуал + деньги) + рекомендации, по приоритету. |
Опора всей цепочки — `superpowers:using-superpowers` (скилы инвокируются ПЕРВЫМИ для своего звена; §12 Pravila — hard rule).
## 🧪 ЧЕРНОВИК ЧЕК-ЛИСТА СЦЕНАРИЕВ (доуточнить в звене 1)
Формы и пути, которые тупой клиент должен пройти без спотыкания:
- **Сбор конкурентов (300 ₽):** пустые поля, 1 пример вместо 2, мусорный сайт, «+ ещё пример», федералы Да/Нет, отмена, повтор клика, баланса не хватает.
- **Добавить конкурента вручную:** без названия, без сайта и без справочника, кривой %, кривые ссылки 2ГИС/Яндекс.
- **Сбор источников (50 ₽):** у конкурента без сайта/справочника, повтор, отмена, пустой результат.
- **Добавить источник вручную:** сайт vs телефон, кривой телефон, переключение типа.
- **Создать проект из источника:** регион/лимит/дни, лимит 0/огромный, все дни сняты, отмена.
- **Настройки проекта / Изменить источник / Сменить источник:** тип залочен, подтверждение смены, тексты сроков (18:00/21:00 МСК).
- **Групповые действия:** ≥2 конкурента (вкл/выкл проекты), ≥2 источника (создать/пауза/возобновить/удалить), смешанный выбор (с проектом и без), «Снять выбор», «Выбрать всех».
- **Удаление:** конкурента с активным проектом (гвард 409), источника с активным проектом, предложения.
- **Предложения:** перенос одного/массово в поле, править/удалить предложение, единое окно сбора.
- **Биллинг:** доп.услуги показаны, списание 300/50 при успехе, НЕ списание при пустом, блок при нехватке баланса; админ-экран тарифов (правка цены + причина ≥30).
- **Визуал каждого экрана** (звено 6): сверка с прототипом, адаптив, пустые/загрузочные состояния.
## 🔒 РАМКИ (жёстко)
- Прод (liderra.ru / Managed PG) и gitea — **НЕ трогать**.
- Тест-БД и правки заглушки Агента — **только стенд**, в прод/коммит не уходят.
- Ничего не коммитить без «эскейп» владельца.
- Простой русский, без жаргона. Любое спотыкание тупого клиента = находка.
## Статус
Согласовано 30.06.2026. Тестирование ещё НЕ начато — стартуем со звена 1 после /compact.
@@ -0,0 +1,168 @@
# Прогон теста «Конкурентного поля» глазами тупого клиента — план
> **Для исполнителя:** это runbook теста, не сборка фичи. Шаги — checkbox для отслеживания.
> Единственная правка кода — звено 4 (заглушка Агента, по TDD, dev-only, НЕ коммитить).
**Цель:** пройти чек-лист A–J ([спека](../specs/2026-06-30-konkurentnoe-pole-dumb-client-test-design.md))
глазами «самого тупого клиента», зафиксировать находки «ожидал → увидел», сверить деньги по базе,
сверить визуал с эталоном.
**Стенд:** воркстри `.claude/worktrees/avtopodbor`, код под `app/`. Свежая изолированная тест-БД
`liderra_dumbtest` (НЕ прод, НЕ phpunit-овая `liderra_testing`). Прод/gitea не трогать.
**Инструменты:** Playwright MCP (клики+скрины), Laravel Boost MCP (деньги по базе),
frontend-design (визуал), субагент-верификатор. Команды — через Bash (Git Bash, env-prefix работает).
---
## Звено 3 — Поднять стенд (свежая изолированная БД)
- [ ] **3.1 Собрать фронт**
`cd .claude/worktrees/avtopodbor/app && npm run build`
Ожидание: `vite build` exit 0.
- [ ] **3.2 Создать свежую БД `liderra_dumbtest`**
Основной: `cd .claude/worktrees/avtopodbor/app && php artisan tinker --execute="DB::statement('DROP DATABASE IF EXISTS liderra_dumbtest'); DB::statement('CREATE DATABASE liderra_dumbtest');"`
Фоллбэк (если CREATE DATABASE в транзакции падает): `createdb -h 127.0.0.1 -U postgres liderra_dumbtest`.
⚠️ Это dev (postgres superuser, CREATEDB есть). Прод-кластер НЕ трогать.
- [ ] **3.3 Миграции на свежую БД**
`cd .claude/worktrees/avtopodbor/app && DB_DATABASE=liderra_dumbtest php artisan config:clear && DB_DATABASE=liderra_dumbtest php artisan migrate --force`
Ожидание: все миграции прошли (включая box, phone_type, autopodbor_*).
- [ ] **3.4 Партиции (если требуются логами/лидами)**
`cd .claude/worktrees/avtopodbor/app && DB_DATABASE=liderra_dumbtest php artisan partitions:create-months`
Если команда называется иначе — найти `php artisan list | grep partition` и выполнить аналог.
- [ ] **3.5 Сид демо-Омеги**
`cd .claude/worktrees/avtopodbor/app && DB_DATABASE=liderra_dumbtest php artisan db:seed --class=OmegaDemoFieldSeeder --force`
Ожидание: тенант `omega-demo@liderra.local` / `omega12345` создан, поле наполнено.
- [ ] **3.6 Поднять приложение** (фоном)
`cd .claude/worktrees/avtopodbor/app && DB_DATABASE=liderra_dumbtest php artisan serve --port=8001`
Ожидание: слушает http://127.0.0.1:8001 .
- [ ] **3.7 Поднять эталон-прототип** (фоном, для визуала)
`node docs/superpowers/prototypes/serve.js` → http://localhost:8123 .
- [ ] **3.8 Зайти клиентом**
Playwright: `browser_navigate` http://127.0.0.1:8001/login → войти omega-demo@liderra.local / omega12345
→ открыть «Конкурентное поле». Снять стартовый скрин.
---
## Звено 4 — Эмуляция поставки Агента (TDD, dev-only, НЕ коммитить)
Заглушка `FakeCompetitorAgent` отдаёт ФИКСИРОВАННЫЕ данные. Чтобы D3 проверил значки ✓/🎭 и типы
номеров, усилить заглушку до реалистичной поставки: конкуренты с разной похожестью + источники с
разными `phone_type` (city/mobile/tollfree) и `phone_kind` (real/substitute). Pest остаётся зелёным.
- [ ] **4.1 Найти файл заглушки**
`grep -rl "class FakeCompetitorAgent" .claude/worktrees/avtopodbor/app/app` — зафиксировать путь и
текущий вид `findCompetitors` / `studyCompetitor`.
- [ ] **4.2 Написать падающий тест** (Pest, real-код, без моков)
В `tests/Feature/Autopodbor/` новый тест: `studyCompetitor` возвращает ≥3 источника, среди них
есть и `phone_type='city'`, и `'mobile'`, и `'tollfree'`, и хотя бы один `phone_kind='substitute'`.
Использовать `uses(SharesSupplierPdo::class)` + `SET LOCAL app.current_tenant_id` при записи.
- [ ] **4.3 Прогнать — убедиться, что падает**
`cd .claude/worktrees/avtopodbor/app && DB_DATABASE=liderra_testing php artisan migrate --force && php vendor/bin/pest tests/Feature/Autopodbor/<NewTest>.php`
Ожидание: FAIL (сейчас типов меньше/однообразны).
- [ ] **4.4 Минимальная правка заглушки**
В `FakeCompetitorAgent::studyCompetitor` вернуть реалистичный набор источников с разными
`phone_type` и `phone_kind`. Только заглушка, прод-интерфейс `CompetitorAgent` не трогать.
- [ ] **4.5 Прогнать — зелёный + регресс**
`php vendor/bin/pest tests/Feature/Autopodbor tests/Unit/Autopodbor`
Ожидание: новый PASS, остальные автоподбор-тесты зелёные.
- [ ] **4.6 Пересобрать фронт и перезалить демо-поле** (чтобы поставка была видна в UI)
`npm run build`; при необходимости пере-сид `OmegaDemoFieldSeeder` на `liderra_dumbtest`.
НИЧЕГО не коммитить.
---
## Звено 5 — Прогон глазами (Playwright), блоки AG
Для каждого пункта: записать **ожидал → увидел**, снять скрин при спотыкании.
- [ ] **5.A** A1 первый взгляд · A2 пустое поле (отдельный пустой тенант/раздел) · A3 баннер-черновик.
- [ ] **5.B** B1 пусто→Поехали · B2 один пример · B3 мусор-сайт · B4 «+ ещё» ×много · B5 федералы ·
B6 двойной клик · B7 Отмена · B8 нехватка баланса. (баланс проверяется в звене 7)
- [ ] **5.C** C1 без названия · C2 пусто · C3 кривой % · C4 кривые ссылки · **C5 находка M3**
(похожесть+ссылки 2ГИС/Яндекс — сохранились?).
- [ ] **5.D** D1 «Собрать источники» 50 ₽ (пусто/повтор/отмена/нехватка) · D2 источник вручную
(сайт/телефон/кривой телефон/тип) · **D3 поставка Агента** — значки ✓/🎭 и тип верны.
- [ ] **5.E** E1 создать проект (лимит 0/огромный/дни сняты/отмена) · E2 Настройки/Изменить/Сменить
источник (тип залочен, подтверждение, тексты 18:00/21:00) · E3 удалить с активным проектом (гвард 409).
- [ ] **5.F** F1 ≥2 конкурента массово · F2 ≥2 источника массово · F3 смешанный выбор ·
F4 выбрать всех/снять/уход с экрана.
- [ ] **5.G** G1 единый вид Поле↔Предложения · G2 перенос один/массово · G3 править/удалить
предложение · G4 «Собрать» из Предложений → окно 300 ₽.
- [ ] **5.J** Админ `/admin/autopodbor-pricing`: J1 сменить цену (причина <30 → блок; ≥30 → сохранение) ·
J2 проверить, что окна сбора у клиента показывают новую цену.
---
## Звено 6 — Визуал (frontend-design + Playwright)
- [ ] **6.1** Каждый экран бок-о-бок :8001 vs :8123 — `browser_take_screenshot` обоих, сравнить
палитру Forest, шрифты, отступы, иерархию.
- [ ] **6.2** Состояния: пустые, спиннеры, модалки, тосты, бейджи (✓/🎭, фед/рег), панель массовых действий.
- [ ] **6.3** Адаптив: `browser_resize` 390×844 (мобильный) — ничего не разъехалось; затем обратно.
- [ ] **6.4** Расхождения палитры/шрифтов/отступов — в находки с скрином.
---
## Звено 7 — Деньги (Laravel Boost database-query)
Для каждого платного действия — баланс ДО/ПОСЛЕ через `mcp__laravel-boost__database-query`
(подключение к `liderra_dumbtest`).
- [ ] **7.1** Снять баланс тенанта ДО (таблица балансов/тенанта).
- [ ] **7.2** H1: после успешного «Собрать конкурентов» — списано ровно 300 ₽; «Собрать источники» — ровно 50 ₽.
- [ ] **7.3** H2: при пустом результате — баланс НЕ изменился (списания нет).
- [ ] **7.4** H3: двойной клик — ровно одно списание, не два (сверить транзакции/идемпотентность).
- [ ] **7.5** H4: при нехватке — действие заблокировано, баланс не ушёл в минус.
- [ ] **7.6** Сверить таблицу транзакций списаний на дубли/«висяки».
---
## Звено 8 — Корень бага (systematic-debugging, только если падает)
- [ ] **8.1** На каждое реальное падение из звеньев 5–7 — отдельная гипотеза, фальсификация командой,
корневая причина. Не «замазывать» симптом.
---
## Звено 9 — Независимая перепроверка (субагент + verification)
- [ ] **9.1** Дать субагенту-скептику (`Explore`/general-purpose) список находок: воспроизвести,
отсеять ложные, подтвердить реальные.
- [ ] **9.2** Прогнать skill `superpowers:verification` по своим выводам — нет ли выданного за
проверенное без доказательства.
- [ ] **9.3** Отранжировать находки по «больно клиенту» (блокер / спотыкание / косметика).
---
## Звено 10 — Отчёт
- [ ] **10.1** Файл `docs/superpowers/findings/2026-06-30-konkurentnoe-pole-dumb-client-RESULTS.md`:
находки (UX + визуал + деньги), по приоритету, со скринами и «ожидал → увидел».
- [ ] **10.2** Рекомендации по улучшению (что и почему).
- [ ] **10.3** Краткий устный итог владельцу простым русским.
---
## Рамки (жёстко)
- Прод (liderra.ru / Managed PG) и gitea — НЕ трогать. БД операций — только `liderra_dumbtest`.
- Правки заглушки Агента — dev-only, в коммит/прод не уходят.
- Ничего не коммитить без «эскейп». Простой русский в отчётах.
## Самопроверка плана
- Покрытие спеки A–J: A→5.A, B→5.B+7, C→5.C, D→4+5.D, E→5.E, F→5.F, G→5.G, H→7, I→6, J→ (добавить ниже).
- **J (админ-тарифы)** — отдельный шаг: после звена 7 зайти в `/admin/autopodbor-pricing`, сменить
цену (причина <30 → блок; ≥30 → сохранение), проверить что окна сбора показывают новую цену. → **5.J**
- Плейсхолдеров нет; пути/команды конкретны (путь заглушки — найти grep в 4.1, это шаг, не плейсхолдер).
@@ -0,0 +1,93 @@
# Тест «Конкурентного поля» глазами «самого тупого клиента» — спека (согласовано 30.06.2026)
Персона: **самый тупой клиент на свете**. Любое место, где он может ошибиться, споткнуться
или не понять — это находка. Глубина **полная**, база **свежая изолированная**.
Связано: [хартия+цепочка скилов](../plans/2026-06-30-konkurentnoe-pole-DUMB-CLIENT-TEST-charter.md) ·
[находки прототип↔реализация](../findings/2026-06-30-konkurentnoe-pole-proto-vs-impl-gap.md) ·
[HANDOFF фичи](../plans/2026-06-29-konkurentnoe-pole-HANDOFF.md) ·
эталон-прототип `docs/superpowers/prototypes/2026-06-29-konkurentnoe-pole-proto.html` (:8123).
Решение владельца 30.06: чек-лист A–J принят целиком («Всё так, фиксируй и поехали») —
**с** эмуляцией поставки Агента (D3), **с** визуалом (I), **без** отдельного сквозного лид→сделка
(не выбран). Эмуляция Агента — dev-only, в прод/коммит не уходит.
---
## Чек-лист сценариев (согласован)
### A. Вход и первый взгляд
- A1 — первый взгляд на «Поле»: понятно ли, что делать.
- A2 — пустое «Поле»: есть ли подсказка «начните отсюда», не пугает ли пустота.
- A3 — баннер-черновик «Лидерра подготовила N в предложениях» — клик ведёт куда ожидаешь.
### B. Сбор конкурентов «Собрать» (платно, 300 ₽)
- B1 — пустые поля → «Поехали»: мягко остановить, НЕ списать.
- B2 — один пример вместо двух.
- B3 — мусор в сайт («ааа», «http://», «телефон сюда»).
- B4 — «+ ещё пример» нажать много раз.
- B5 — федералы Да/Нет — переключения.
- B6 — двойной клик «Поехали» → не списать 300 дважды.
- B7 — «Отмена» на полпути → деньги целы.
- B8 — баланса не хватает → блок, не в минус.
### C. Добавить конкурента вручную
- C1 — без названия. C2 — пусто (без сайта и справочника).
- C3 — кривой % (5, 999, буквы). C4 — кривые ссылки 2ГИС/Яндекс.
- C5 — проверка находки **M3**: похожесть + ссылки 2ГИС/Яндекс сохранились или молча пропали.
### D. Изучение конкурента → источники (платно, 50 ₽)
- D1 — «Собрать источники» (50 ₽): пусто / повтор / отмена / нехватка денег (аналог B1–B8).
- D2 — добавить источник вручную: сайт vs телефон, кривой телефон, переключение типа
(городской/мобильный/8-800 + настоящий ✓ / подменный 🎭).
- D3 — **эмуляция Агента**: «поставка» вернула реалистичные источники с разными типами
телефонов — значки ✓/🎭 и тип отображаются верно.
### E. Проекты из источника
- E1 — создать проект: регион/лимит/дни; лимит 0; лимит огромный; все дни сняты; отмена.
- E2 — Настройки / Изменить источник / Сменить источник: тип залочен; подтверждение смены;
тексты сроков 18:00 / 21:00 МСК не путают.
- E3 — удалить конкурента/источник с активным проектом → мягкий гвард 409.
### F. Групповые действия
- F1 — ≥2 конкурента: вкл/выкл проекты массово.
- F2 — ≥2 источника: создать/пауза/возобновить/удалить массово.
- F3 — смешанный выбор (с проектом и без).
- F4 — «Выбрать всех» / «Снять выбор»; уход на другой экран — сброс выбора.
### G. Предложения (доведённая находка F1)
- G1 — вид «Поле» ↔ «Предложения» единый, не «прыгает».
- G2 — перенос одного / массово «в поле».
- G3 — править / удалить предложение в списке.
- G4 — «Собрать конкурентов» из «Предложений» открывает то же окно 300 ₽.
### H. Деньги (сверка по базе, звено 7)
- H1 — баланс ДО/ПОСЛЕ каждого платного действия (300/50).
- H2 — пустой результат → не списали.
- H3 — двойной клик → списали один раз.
- H4 — нехватка → блок, не в минус.
### I. Визуал (звено 6, обязателен)
- I1 — каждый экран бок-о-бок с :8123: палитра Forest, шрифты, отступы, иерархия.
- I2 — пустые состояния, спиннеры, модалки, тосты, бейджи, панель массовых действий.
- I3 — адаптив: сжать до мобильного — ничего не разъехалось.
### J. Админ (тарифы)
- J1 — экран «Тарифы «Конкурентного поля»»: смена цены; причина <30 (не даёт); причина ОК → сохранил.
- J2 — цены подхватились на клиентской стороне (300/50 в окнах сбора).
---
## Критерии успеха
- Каждый пункт A–J пройден глазами клиента (Playwright) и зафиксирован «ожидал → увидел».
- Деньги (H) сверены по базе ДО/ПОСЛЕ; ни одного ложного/двойного списания, ни одного минуса.
- Визуал (I) сверен с эталоном; расхождения палитры/шрифтов/отступов — в находки.
- Все находки независимо перепроверены (звено 9) и отранжированы по «больно клиенту».
## Рамки
- Прод (liderra.ru / Managed PG) и gitea — НЕ трогать.
- Тест-БД и правки заглушки Агента — только стенд; в прод/коммит не уходят.
- Ничего не коммитить без «эскейп» владельца. Простой русский в отчётах.
## Статус
Спека согласована 30.06.2026. Следующий шаг — звено 2 (writing-plans): пошаговый сценарий прогона.