diff --git a/DUMB-A-field-first-look.png b/DUMB-A-field-first-look.png new file mode 100644 index 00000000..4dc4184b Binary files /dev/null and b/DUMB-A-field-first-look.png differ diff --git a/DUMB-B1-empty-submit.png b/DUMB-B1-empty-submit.png new file mode 100644 index 00000000..a350da52 Binary files /dev/null and b/DUMB-B1-empty-submit.png differ diff --git a/DUMB-B8-insufficient.png b/DUMB-B8-insufficient.png new file mode 100644 index 00000000..362cd026 Binary files /dev/null and b/DUMB-B8-insufficient.png differ diff --git a/DUMB-D3-phone-badges.png b/DUMB-D3-phone-badges.png new file mode 100644 index 00000000..955e2eb4 Binary files /dev/null and b/DUMB-D3-phone-badges.png differ diff --git a/DUMB-D3-phone-types.png b/DUMB-D3-phone-types.png new file mode 100644 index 00000000..ed10f52d Binary files /dev/null and b/DUMB-D3-phone-types.png differ diff --git a/DUMB-I3-mobile-field.png b/DUMB-I3-mobile-field.png new file mode 100644 index 00000000..32db1e99 Binary files /dev/null and b/DUMB-I3-mobile-field.png differ diff --git a/DUMB-J-admin-pricing.png b/DUMB-J-admin-pricing.png new file mode 100644 index 00000000..78b3b34e Binary files /dev/null and b/DUMB-J-admin-pricing.png differ diff --git a/docs/superpowers/findings/2026-06-30-konkurentnoe-pole-dumb-client-RESULTS.md b/docs/superpowers/findings/2026-06-30-konkurentnoe-pole-dumb-client-RESULTS.md new file mode 100644 index 00000000..ddd62929 --- /dev/null +++ b/docs/superpowers/findings/2026-06-30-konkurentnoe-pole-dumb-client-RESULTS.md @@ -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`. diff --git a/docs/superpowers/plans/2026-06-30-konkurentnoe-pole-DUMB-CLIENT-TEST-charter.md b/docs/superpowers/plans/2026-06-30-konkurentnoe-pole-DUMB-CLIENT-TEST-charter.md new file mode 100644 index 00000000..6eb27c09 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-konkurentnoe-pole-DUMB-CLIENT-TEST-charter.md @@ -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. diff --git a/docs/superpowers/plans/2026-06-30-konkurentnoe-pole-dumb-client-test-RUN.md b/docs/superpowers/plans/2026-06-30-konkurentnoe-pole-dumb-client-test-RUN.md new file mode 100644 index 00000000..226e9558 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-konkurentnoe-pole-dumb-client-test-RUN.md @@ -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/.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), блоки A–G + +Для каждого пункта: записать **ожидал → увидел**, снять скрин при спотыкании. + +- [ ] **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, это шаг, не плейсхолдер). diff --git a/docs/superpowers/specs/2026-06-30-konkurentnoe-pole-dumb-client-test-design.md b/docs/superpowers/specs/2026-06-30-konkurentnoe-pole-dumb-client-test-design.md new file mode 100644 index 00000000..688a80c4 --- /dev/null +++ b/docs/superpowers/specs/2026-06-30-konkurentnoe-pole-dumb-client-test-design.md @@ -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): пошаговый сценарий прогона.