fix(реклама): пачка 5 — приёмка глазами: 20 значков рисовались вопросом в кружке

Приёмка обоих рекламных экранов на локальном портале. Экраны работают:
живой счётчик охвата правда ходит на сервер, ручка оценки правда только
читает (после её вызовов ноль черновиков в базе), заголовок объявления сам
появляется при ссылке на сайт, «Запустить» не нажимается при охвате меньше
367, «Принято, показов пока нет» и «часть объявлений отклонена — 2 из 15»
на месте.

Находка, которой не видел ни один из 1829 зелёных тестов: значки в портале
не встроенные, имена mdi-* вручную сопоставлены с набором Lucide, а
незнакомое имя молча подменяется на HelpCircle. Клиент видит вопрос в
кружке вместо значка. Замер: 43 имени из 162 по всему порталу.

На рекламных экранах таких было 20 — включая заголовок телеграм-экрана,
ярлыки «Проверяем вручную» и «Запущена в кабинете МТС», поле «Картинка или
видео» и ярлык «Черновик» у Яндекса. Шесть из них добавил я в пачках 1–4,
четырнадцать ломались раньше. Все 20 починены, проверено глазами после
пересборки.

Заведён сторож tests/Frontend/znachki-est-v-karte.spec.ts: краснеет на
любом имени вне карты и называет файл-виновника. До починки был красным
ровно на тех 20 — проверено вырезанием. Оставшиеся 23 сломанных имени на
экранах вне рекламы записаны в нём списком известного долга; третья
проверка следит, чтобы список не протух.

Карта значков открыта наружу (export) ради сторожа — поведение не меняется.

Замеры: экраны 241 файл / 1832 теста / 0 падений (было 240/1829),
Advertising 435, ClientTg 292, статанализ 0, pint чисто, типы — те же 6
чужих ошибок. На боевой ничего не катал.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-02 19:14:11 +03:00
parent dbc79f15b3
commit 3d728a2bab
4 changed files with 239 additions and 5 deletions
+47 -1
View File
@@ -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<string, Component> = {
export const lucideMap: Record<string, Component> = {
'mdi-account-arrow-right-outline': UserCheck,
'mdi-account-group-outline': Users,
'mdi-account-outline': User,
@@ -273,6 +293,32 @@ const lucideMap: Record<string, Component> = {
'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 = {
@@ -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<string, string[]> {
const najdeno = new Map<string, string[]>();
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([]);
});
});
@@ -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 × 793849) **налезает**
на кнопку «Дальше» (785–873 × 792–828) — проверено координатами, не на глаз. Перекрытие около
35×35 точек по правому краю кнопки.
Виджет чата общий для всего портала и висит там давно; чинить его молча в этой работе не стали.
**Решение за владельцем.**
🪤 Ловушка при проверке: на снимке **всей страницы** прилипшие элементы рисуются не на своём
месте. Сперва мне показалось, что пузырь перекрывает кнопку и на других экранах, — по
координатам это не подтвердилось. Такие находки проверять замером, а не снимком.
#### 🟡 Находка 3 — подсказка «Выберите, кому показывать» не про то
На шаге 1 при выборе «Свой список номеров», пока список пуст, под ним написано
«Выберите, кому показывать, — посчитаем охват.» Но выбор-то уже сделан — не хватает самих
номеров. Поломкой не выглядит, но фраза не про ту ситуацию. Мелочь, **решение за владельцем.**
#### Что осталось за приёмкой глазами
**Боевой не смотрели.** Всё выше — локальная версия. На боевом экраны выглядят иначе как
минимум одним: там нет полосы «Режим песочницы» (`TG_SANDBOX=false` с 02.08).
---
@@ -28,6 +28,12 @@
Две части: **сперва принять глазами, потом выкатить**. Между ними — «да» владельца.
> ✅ **Часть 1 сделана 02.08.2026 на ЛОКАЛЬНОМ портале.** Разбор находок — в главном документе,
> раздел «Пачка 5». Коротко: экраны работают, живой счётчик и ручка оценки проверены живьём;
> нашлась беда со значками (43 имени из 162 рисовались вопросом в кружке — 20 починено, заведён
> сторож), плюс две мелочи на решение владельца. **Боевой не смотрели** — там нужен доступ
> владельца. Осталась Часть 2 — выкат.
### Часть 1. Приёмка глазами — обязательно ДО выката
Ни одна из пяти пачек глазами не принята. Тесты зелёные, но **зелёный тест не доказывает,