diff --git a/app/resources/js/plugins/vuetify.ts b/app/resources/js/plugins/vuetify.ts index c256ff8a..d17e0cff 100644 --- a/app/resources/js/plugins/vuetify.ts +++ b/app/resources/js/plugins/vuetify.ts @@ -13,13 +13,17 @@ import { ArrowLeft, ArrowRightLeft, ArrowUp, + Ban, Bell, BellOff, Bot, Calendar, + CalendarClock, CalendarDays, + CalendarRange, Camera, Check, + CheckCheck, CheckCircle, ChevronDown, ChevronLeft, @@ -30,6 +34,8 @@ import { ChevronUp, Circle, CircleDot, + CirclePause, + CirclePlay, CircleStop, Clock, Code, @@ -40,18 +46,22 @@ import { Eye, EyeOff, FilterX, + FileCheck, FileSpreadsheet, FileText, + FileUp, FlaskConical, Folder, Folders, Globe, HelpCircle, + Image as ImageIcon, Info, Key, KeyRound, LayoutDashboard, LifeBuoy, + Link2, List, LogOut, Mail, @@ -71,12 +81,16 @@ import { Puzzle, ReceiptText, RefreshCw, + Repeat, + Repeat2, + Rocket, RotateCcw, RotateCw, RussianRuble, Save, Search, Send, + SendHorizontal, Settings, Shield, ShieldCheck, @@ -95,6 +109,7 @@ import { UserCog, UserPlus, Users, + UserSearch, UserX, Wallet, Webhook, @@ -148,8 +163,13 @@ const liderraForest: ThemeDefinition = { * как semantic identifiers, рендерятся через Lucide stroke-based SVG icons. * * CLAUDE.md §2 «Иконки: Lucide» — бренд-spec compliance. + * + * Экспортируется наружу РАДИ СТОРОЖА: имя, которого тут нет, молча подменяется + * на HelpCircle (см. ниже), и на экране вместо значка появляется вопрос в кружке. + * Замер 02.08.2026: так молча ломались 43 имени из 162. Сторож — + * tests/Frontend/znachki-est-v-karte.spec.ts. */ -const lucideMap: Record = { +export const lucideMap: Record = { 'mdi-account-arrow-right-outline': UserCheck, 'mdi-account-group-outline': Users, 'mdi-account-outline': User, @@ -273,6 +293,32 @@ const lucideMap: Record = { 'mdi-star': Star, 'mdi-star-outline': Star, 'mdi-star-half-full': StarHalf, + + // ——— Рекламные экраны (приёмка глазами 02.08.2026) ——— + // Этих имён в карте не было, и на экране вместо значка стоял вопрос в + // кружке: у ярлыков «Проверяем вручную» и «Запущена в кабинете МТС», у поля + // «Картинка или видео», у заголовка телеграм-экрана. Ярлык состояния с + // вопросом вместо значка читается как «портал сам не знает, что происходит». + 'mdi-account-search-outline': UserSearch, + 'mdi-calendar-clock': CalendarClock, + 'mdi-calendar-range': CalendarRange, + 'mdi-cancel': Ban, + 'mdi-check-all': CheckCheck, + 'mdi-file-check-outline': FileCheck, + 'mdi-file-outline': FileText, + 'mdi-file-upload-outline': FileUp, + 'mdi-help-circle-outline': HelpCircle, + 'mdi-image-outline': ImageIcon, + 'mdi-link-variant': Link2, + 'mdi-pause-circle': CirclePause, + 'mdi-play-circle': CirclePlay, + 'mdi-repeat': Repeat, + 'mdi-repeat-variant': Repeat2, + 'mdi-rocket-launch-outline': Rocket, + 'mdi-send-check-outline': SendHorizontal, + 'mdi-send-circle-outline': Send, + 'mdi-tag-outline': Tag, + 'mdi-upload': Upload, }; const liderraLucideSet: IconSet = { diff --git a/app/tests/Frontend/znachki-est-v-karte.spec.ts b/app/tests/Frontend/znachki-est-v-karte.spec.ts new file mode 100644 index 00000000..454862d6 --- /dev/null +++ b/app/tests/Frontend/znachki-est-v-karte.spec.ts @@ -0,0 +1,115 @@ +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import { lucideMap } from '../../resources/js/plugins/vuetify'; + +/** + * Сторож значков. + * + * Значки в портале не встроенные: имена вида `mdi-*` вручную сопоставлены с + * компонентами Lucide в resources/js/plugins/vuetify.ts. Незнакомое имя НЕ + * роняет сборку и НЕ пишет предупреждения — оно молча подменяется на HelpCircle, + * и клиент видит вопрос в кружке вместо значка. + * + * Именно так это и вскрылось: 02.08.2026 приёмка глазами показала вопрос в + * кружке у ярлыков «Проверяем вручную» и «Запущена в кабинете МТС», у поля + * «Картинка или видео» и у самого заголовка телеграм-экрана. Все 1829 тестов + * экранов при этом были зелёными — проверять было нечем. + * + * Сторож закрывает класс: любое новое имя обязано быть в карте. + */ + +const kornevayaPapka = path.resolve(__dirname, '../../resources/js'); + +/** Имена значков, сломанные ДО этой работы, на экранах вне рекламы. */ +const IZVESTNYY_DOLG = [ + 'mdi-account-check-outline', + 'mdi-account-clock-outline', + 'mdi-account-eye-outline', + 'mdi-account-multiple-outline', + 'mdi-account-plus', + 'mdi-alpha-v-box-outline', + 'mdi-alpha-v-circle', + 'mdi-alpha-y-circle', + 'mdi-bullseye-arrow', + 'mdi-cash-remove', + 'mdi-chart-line', + 'mdi-clock-edit-outline', + 'mdi-close-circle-outline', + 'mdi-file-document-check-outline', + 'mdi-filter-variant', + 'mdi-history', + 'mdi-map-search-outline', + 'mdi-message-text-outline', + 'mdi-open-in-new', + 'mdi-pause-circle-outline', + 'mdi-radar', + 'mdi-send-circle', + 'mdi-undo', +]; + +function sobratFayly(papka: string): string[] { + const itog: string[] = []; + for (const zapis of fs.readdirSync(papka, { withFileTypes: true })) { + const polnyy = path.join(papka, zapis.name); + if (zapis.isDirectory()) { + itog.push(...sobratFayly(polnyy)); + } else if (zapis.name.endsWith('.vue') || zapis.name.endsWith('.ts')) { + itog.push(polnyy); + } + } + return itog; +} + +/** Где какое имя встречается — чтобы сторож называл виновника поимённо. */ +function sobratImena(): Map { + const najdeno = new Map(); + for (const fayl of sobratFayly(kornevayaPapka)) { + // Сама карта — не потребитель значков, а их список. + if (fayl.endsWith(path.join('plugins', 'vuetify.ts'))) continue; + + const tekst = fs.readFileSync(fayl, 'utf-8'); + for (const sovpadenie of tekst.matchAll(/mdi-[a-z0-9-]+/g)) { + const imya = sovpadenie[0]; + const gde = path.relative(kornevayaPapka, fayl).replace(/\\/g, '/'); + const spisok = najdeno.get(imya) ?? []; + if (!spisok.includes(gde)) spisok.push(gde); + najdeno.set(imya, spisok); + } + } + return najdeno; +} + +const ispolzuemye = sobratImena(); + +describe('значки: каждое имя mdi-* есть в карте Lucide', () => { + it('экраны вообще используют значки — иначе сторож охраняет пустоту', () => { + expect(ispolzuemye.size).toBeGreaterThan(100); + }); + + it('ни одного нового имени вне карты (иначе на экране вопрос в кружке)', () => { + const bezZnachka: string[] = []; + for (const [imya, fajly] of ispolzuemye) { + if (imya in lucideMap) continue; + if (IZVESTNYY_DOLG.includes(imya)) continue; + bezZnachka.push(`${imya} → ${fajly.join(', ')}`); + } + + expect( + bezZnachka, + 'Эти имена рисуются вопросом в кружке. Добавьте их в lucideMap ' + + '(resources/js/plugins/vuetify.ts) или возьмите имя, которое там уже есть:\n' + + bezZnachka.join('\n'), + ).toEqual([]); + }); + + it('список известного долга не протух: каждое имя в нём всё ещё используется и всё ещё вне карты', () => { + const lishnie = IZVESTNYY_DOLG.filter((imya) => !ispolzuemye.has(imya) || imya in lucideMap); + + expect( + lishnie, + 'Эти имена больше не нужны в списке долга — уберите их оттуда, ' + + 'иначе список врёт о размере беды:\n' + lishnie.join('\n'), + ).toEqual([]); + }); +}); diff --git a/docs/superpowers/2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md b/docs/superpowers/2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md index 22097f08..39d565f6 100644 --- a/docs/superpowers/2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md +++ b/docs/superpowers/2026-08-02-ZAMECHANIYA-edinyy-vid-reklamnyh-ekranov.md @@ -386,11 +386,78 @@ webm/mkv/mpeg/wmv проверяются формат и вес, остальн ошибок вместо прежних 7 (одна исчезла вместе с удалённым тестом), ни одной своей; формат PHP чист. **Глазами не принято — приёмка в пачке 5.** -### Пачка 5 — сверка и приёмка +### Пачка 5 — сверка и приёмка — ✅ ПРИЁМКА ГЛАЗАМИ ПРОЙДЕНА 02.08.2026 (на боевой НЕ выкачено) -- пройти оба экрана глазами подряд, как клиент; -- прогнать оба набора тестов; -- обновить снимок боевого и промт следующей смене. +Приёмка шла на **локальном** портале (`127.0.0.1:8765`, база `liderra` на своей машине, вход +`admin@demo.local` / `password`). Боевой не трогали: демо-входа там нет с 01.07, доступ для +приёмки на бою нужно просить у владельца. + +Чтобы было на что смотреть, в **локальную** базу заведены напоказ: 4 телеграм-кампании +(`launched` / `moderating` / `rejected` / `needs_review`, одна с нарочно длинным текстом), +1 кампания Яндекса `running` с нулём показов и 15 объявлениями, из них 2 `REJECTED`. +Убрать: `delete from client_tg_campaigns where tenant_id=1; delete from ad_campaigns where id=11;` + +**Что подтвердилось живьём (не тестом):** + +- живой счётчик охвата **правда ходит на сервер** — два запроса `POST /campaigns/estimate` + (при открытии и после правки поля), оба 200; +- ручка оценки **правда только читает**: после её вызовов `select count(*) from + client_tg_campaigns` = 0, черновик не появился; +- «Заголовок объявления» **сам появляется**, как только ссылка становится сайтом, с пояснением + про требование МТС (пачка 1); +- «Запустить» **не нажимается** при охвате меньше 367 (проверено свойством `disabled`, не глазами); +- «Принято, показов пока нет» и приписка «часть объявлений отклонена — 2 из 15» — пачка 2 + работает, замечание З-6 закрыто; +- миграция пачки 1 прогоняется чисто (проверено на локальной базе). + +#### 🔴 Находка 1 — значки: 43 имени из 162 рисовались вопросом в кружке + +Значки в портале **не встроенные**: имена вида `mdi-*` вручную сопоставлены с набором Lucide в +`resources/js/plugins/vuetify.ts`. Незнакомое имя не роняет сборку и ничего не пишет в журнал — +оно молча подменяется на `HelpCircle`. Клиент видит **вопрос в кружке вместо значка**. + +Замер 02.08: сломаны **43 имени из 162** по всему порталу. На рекламных экранах — 20 из них, и +это было видно на самом заметном: у заголовка телеграм-экрана, у ярлыков «Проверяем вручную» и +«Запущена в кабинете МТС», у поля «Картинка или видео», у ярлыка «Черновик» на экране Яндекса. +Ярлык состояния с вопросом читается как «портал сам не знает, что происходит». + +Из 20 — **6 добавил я** в пачках 1–4, 14 ломались **до** этой работы. + +**Починено 20** (все, что встречаются на рекламных экранах; попутно вылечились и несколько +других экранов, где те же имена). **Осталось 23** имени на экранах вне рекламы — +это отдельная работа, решать владельцу. + +Заведён сторож `app/tests/Frontend/znachki-est-v-karte.spec.ts`: он краснеет на любом имени вне +карты и называет файл-виновника. До починки был красным ровно на тех 20 — проверено. Оставшиеся +23 записаны в нём списком известного долга, и третья проверка сторожа следит, чтобы этот список +не протух (имя, которое починили или перестали использовать, обязано из него уйти). + +**Почему этого не поймали 1829 зелёных тестов:** проверять было нечем — ни один тест не смотрел, +существует ли значок с таким именем. + +#### 🟡 Находка 2 — пузырь чата поддержки перекрывает кнопку мастера + +Внизу мастера, когда страница прокручена до конца, пузырь чата (838–894 × 793–849) **налезает** +на кнопку «Дальше» (785–873 × 792–828) — проверено координатами, не на глаз. Перекрытие около +35×35 точек по правому краю кнопки. + +Виджет чата общий для всего портала и висит там давно; чинить его молча в этой работе не стали. +**Решение за владельцем.** + +🪤 Ловушка при проверке: на снимке **всей страницы** прилипшие элементы рисуются не на своём +месте. Сперва мне показалось, что пузырь перекрывает кнопку и на других экранах, — по +координатам это не подтвердилось. Такие находки проверять замером, а не снимком. + +#### 🟡 Находка 3 — подсказка «Выберите, кому показывать» не про то + +На шаге 1 при выборе «Свой список номеров», пока список пуст, под ним написано +«Выберите, кому показывать, — посчитаем охват.» Но выбор-то уже сделан — не хватает самих +номеров. Поломкой не выглядит, но фраза не про ту ситуацию. Мелочь, **решение за владельцем.** + +#### Что осталось за приёмкой глазами + +**Боевой не смотрели.** Всё выше — локальная версия. На боевом экраны выглядят иначе как +минимум одним: там нет полосы «Режим песочницы» (`TG_SANDBOX=false` с 02.08). --- diff --git a/docs/superpowers/2026-08-06-PROMT-priyomka-i-vykat-reklamnyh-ekranov.md b/docs/superpowers/2026-08-06-PROMT-priyomka-i-vykat-reklamnyh-ekranov.md index 42883f6e..86d75000 100644 --- a/docs/superpowers/2026-08-06-PROMT-priyomka-i-vykat-reklamnyh-ekranov.md +++ b/docs/superpowers/2026-08-06-PROMT-priyomka-i-vykat-reklamnyh-ekranov.md @@ -28,6 +28,12 @@ Две части: **сперва принять глазами, потом выкатить**. Между ними — «да» владельца. +> ✅ **Часть 1 сделана 02.08.2026 на ЛОКАЛЬНОМ портале.** Разбор находок — в главном документе, +> раздел «Пачка 5». Коротко: экраны работают, живой счётчик и ручка оценки проверены живьём; +> нашлась беда со значками (43 имени из 162 рисовались вопросом в кружке — 20 починено, заведён +> сторож), плюс две мелочи на решение владельца. **Боевой не смотрели** — там нужен доступ +> владельца. Осталась Часть 2 — выкат. + ### Часть 1. Приёмка глазами — обязательно ДО выката Ни одна из пяти пачек глазами не принята. Тесты зелёные, но **зелёный тест не доказывает,