314 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 59482cae85 |
fix(mera): витрина сделок в расписании, дата прогона на странице (#3469)
Some checks failed
CI Trade-In / frontend-checks (pull_request) Successful in 1m57s
CI Trade-In / backend-tests (pull_request) Failing after 5m27s
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 16s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Задача landing_showcase_deals не запускалась вообще: строки в scrape_schedules не было (0 строк по '%showcase%' на проде 12.09), а в реестре product_handlers — обработчика. Пересчёт был ручным шагом, и лэндинг показывал прогон от 30.08 — тринадцать суток. Что сделано: - Handler `landing_showcase_deals` в product_handlers: тело задачи писалось под `python -m` и про run_id не знает, поэтому done/failed ставит обработчик (как у refresh_search_matview). - Миграция 303 сеет расписание: enabled=true, окно 06:00–07:00 UTC, interval_days=1. Такт суточный не из-за данных — сделки Росреестра квартальные, — а из-за кода: прогноз считает тот же спайн оценщика, что и боевой расчёт, и любой деплой меняет числа на витрине, не трогая ни одной сделки. - Эта же строка заводит витрину в СУЩЕСТВУЮЩИЙ монитор свежести: сводка просроченных источников (emit_stale_digest, #2670) ходит по включённым расписаниям и бьёт ERROR → GlitchTip, когда источник молчит дольше 3× своего такта. Своего монитора не заводим: витрина была невидима не потому, что сводка не умеет про неё говорить, а потому, что источника для сводки не существовало. - На странице под таблицей — дата прогона рядом со счётчиками: «Витрина пересчитана 30.08.2026». computed_at ручка /showcase отдавала и раньше, фронт его не показывал; даты нет — предложения нет. Тесты: обработчик резолвится тем же resolve_handler, что и боевой _dispatch; миграция на живой БД реально кладёт строку и не задваивает её при повторе; настоящий запрос сводки видит витрину и отдаёт её просроченной после 3× такта (число тактов — литерал из приёмки, не константа кита: взятое из неё ожидание уезжало вместе с ней и держало тесты зелёными при факторе 3650). Closes #3469 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
d5c876e3d0 |
fix(tradein): включаем idempotency-key на фронте, чиним assert-crash и честность докстрингов (#3471)
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 7m35s
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m10s
Ревью PR #3495 нашло, что механизм был мёртвым кодом: фронт не отправлял Idempotency-Key ни в одном запросе, весь прод-трафик шёл по ненадёжному fallback-отпечатку. Плюс два "assert" в record_inbound после ON CONFLICT давали AssertionError (не ловится except SQLAlchemyError) уже ПОСЛЕ доставки в Telegram — под `python -O` assert и вовсе исчезает. - useSupportChat.ts: useSendSupportMessage генерирует Idempotency-Key (crypto.randomUUID()) на намерение отправить, переиспользует его при повторной отправке ТОГО ЖЕ текста, сбрасывает на успехе. - web_support_storage.record_inbound: assert -> явные ветки с логом; логируем отброшенный topic_message_id проигравшего гонку (не молча). - Докстринги/комментарии переписаны честно: что именно закрывает pre-check (ответ клиенту потерян / двойной клик после успеха), а что НЕ закрывает (сетевую потерю на плече Selectel -> Telegram — там сообщение просто не доставлено, повтор это законная первая попытка). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY6iWDnGDthdvsMWgK1BMG |
||
| c3840019c5 |
Merge pull request 'Москва в реестре городов лендинга и кабинета' (#3472) from feat/msk-public-city-registry into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 15s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 3m3s
Deploy Trade-In / test (push) Successful in 5m21s
Deploy Trade-In / build-backend (push) Successful in 53s
Deploy Trade-In / deploy (push) Successful in 1m52s
Deploy Trade-In / deploy-status (push) Successful in 3s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m50s
|
|||
| 42daf8404f |
Merge pull request 'feat(mera/лендинг): витрина показывает полосу расхождения −5…+20 %; плитку «уверенность низкая» сменил замер 12.09' (#3468) from feat/landing-showcase-band into main
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
Deploy Trade-In / build-frontend (push) Has been cancelled
|
|||
| db47fa0ecd |
fix(mera/лендинг): утверждение про полосу проверяет само себя по показанным строкам
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m48s
CI Trade-In / backend-tests (pull_request) Successful in 6m0s
Дыра в выкате, найденная ревьюером до мержа. Подпись про полосу собиралась из констант `BAND_MIN_PCT`/`BAND_MAX_PCT` в коде фронта, а строки витрины и `rejection_rule` приезжают из БД, от ПОСЛЕДНЕГО прогона задачи `landing_showcase_deals`. Задачи нет в расписании — её запускают руками. Значит в окне «фронт выкачен, витрина не пересчитана» страница утверждала бы «показаны сделки с расхождением от −5 % до +20 %», а под утверждением лежали бы прежние двадцать строк: по замеру на проде 12 из 20 вне полосы, худшая +75,7 %. Утверждение и его опровержение в одном экране — хуже, чем было до правки. Чинится конструкцией, а не запуском задачи: `allWithinBand(deals)` в `deal-view.ts` спрашивает САМИ показанные строки теми же границами, что стоят в тексте. * Все показанные строки в полосе — печатаем прежнюю формулировку. * Хоть одна вне — про полосу НЕ утверждаем. В таблице: «Полосу расхождения от -5 % до +20 % эта подпись не обещает: среди показанных строк есть расхождения вне неё, то есть витрину собрал прогон с другим правилом — тем, что напечатано выше». Правило того прогона и так приезжает в `rejection_rule` из ТОГО ЖЕ прогона, что и строки, поэтому подпись с ними согласована по построению. В ленте остаётся только то, что посчитано по строкам: медиана и худшая. * Медиана по ВСЕЙ сверке (15,3 %, 325 сделок) печатается в обеих ветках — она и удерживает страницу честной независимо от того, пересчитана витрина. Тесты по значению в обе стороны: набор с одной строкой вне полосы (+75,71 % — реальная строка прода) → утверждения про полосу нет; все в полосе → есть. Фальсификация: `allWithinBand` обезврежен руками (всегда true) — краснеют оба новых теста, и красный текст показывает ровно тот дефект: «Это отобранная полоса расхождения от -5 % до +20 % … худшая 75,7 %». Проверка возвращена. Проверка заодно поймала мои же фикстуры ленты: −11,5 % ниже нижней границы полосы (−5 %), то есть «маленькое отклонение» ещё не значит «в полосе». Значения заменены на внутриполосные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
c6711d05c4 |
feat(mera-public): Москва в реестре городов лендинга и кабинета
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m20s
CI Trade-In / backend-tests (pull_request) Successful in 6m2s
Реестр городов один на публичную форму и кабинет, и до сих пор он знал только Свердловскую область. Московский адрес нельзя было выбрать ни там, ни там, хотя бэкенд Москву поддерживает: реестр регионов знает 77, проба покрытия знает московские центроиды, оценка отрабатывает. Москва подана НЕ как ещё один «частично покрытый город области», а отдельной строкой: сбор по ней есть, а замера полноты покрытия нет, и приписывать ей формулировки области было бы неправдой. Для этого у `OblastCity` появилось поле `region`, а `SECONDARY_CITIES` теперь строится из `OBLAST_66_CITIES` — иначе Москва попала бы в перечисление городов области. Бэкенду поле не отправляется: это различение нужно только фронту. В `landing-facts.ts` строки Москвы сознательно нет — там лежат замеры покрытия по городам, а по Москве замера не делали. Придумывать цифру нельзя, поэтому паритет-тест копи сверяет замеры с `OBLAST_66_CITIES`. Тексты про географию переписаны в четырёх местах: плашка покрытия на главной, карточка бесплатной пробы, ответ FAQ про регионы и сообщение «адрес вне покрытия». Везде одна и та же честная формулировка: по области — полное и частичное покрытие, по Москве — считаем, но полноту не мерили. Юридический адрес в подвале не трогали, там «Свердловская область» — это адрес компании, а не география сервиса. Паритет-тест дропдауна и порогов покрытия на бэкенде дополнен Москвой: город, предлагаемый к выбору, обязан быть отвечаемым пробой. Фронт: 218 passed, tsc и eslint чистые. Бэкенд: 34 passed в затронутом файле. |
||
| 2467943200 |
feat(mera/лендинг): витрина показывает полосу расхождения −5…+20 %, плитку уверенности сменил замер 12.09
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m13s
CI Trade-In / backend-tests (pull_request) Successful in 5m29s
Владелец просит на витрине только сделки, где прогноз разошёлся с ценой ДКП в пределах от −5 % до +20 %. Фильтр живёт в продюсере (`select_rows`), поэтому таблица сверок и бегущая строка берут ОДИН набор, а не два. Чтобы страница от этого не начала врать: * `REJECTION_RULE` переписан. Прежняя формулировка («величина отклонения на отбор и отбраковку не влияет — иначе витрина показывала бы лучший хвост») после фильтра стала ложью ровно про то, чего опасалась, поэтому снята, а не смягчена. Новая называет полосу и говорит, что это отбор показательных строк, а не вся сверка. Границы в текст ПОДСТАВЛЯЮТСЯ из констант `BAND_MIN_ERR_PCT`/`BAND_MAX_ERR_PCT` — подпись не может разъехаться с фильтром, и это проверяется тестом. * Фильтр стоит в `select_rows`, а не в `build_row`: строка вне полосы остаётся кандидатом и попадает в `eligible`. Отбраковав её раньше, мы получили бы «показано 20 из 20 годных» — счётчик, из которого отбор не виден вообще. * Счётчики разъехались с подписью, и подпись поправлена: `eligible − written` больше не значит «столько не поместилось», в разницу входят отсеянные полосой. Под таблицей теперь «показано N строк из M собранных прогоном». * «В пределах 20 % — N из N» из подписи снято: при потолке полосы +20 счёт всегда выходил бы N из N и читался бы как замер попадания. Неработающая проверка читается как работающая. * Медиана по ВСЕЙ сверке (15,3 %, 325 сделок) в подписи осталась и теперь сторожится тестом: без неё разброс отобранной двадцатки читается как точность расчёта. * Полоса названа и в подписи ленты — она висит над первым экраном, её числа читают раньше любых оговорок блока «Точность». * Меньше лимита в полосе — показываем сколько есть, добора нет. Плитка «400 из 400 расчётов с пометкой „уверенность низкая“» заменена на свежий замер 12.09.2026 (engine=full, 290 сделок, медиана трёх пересборок с солями 11/22/33): «52,7 % сделок — расхождение в пределах ±20 %». Запись `confidenceLow` не удалена, а помечена снятой (прогон 29.08 на кластеризованной выборке) — до решения владельца. Оговорки новой величины называют три вещи, без которых она льстит: замер не point-in-time, разброс пересборок 46,2–56,6 %, и что медианное расхождение того же прогона (19,1 %) ВЫШЕ прежних 15,3 % от 31.08 — на странице два числа разных дат, и молчать о том, что свежий прогон вышел хуже, нельзя. `priceError` и `coverage` не тронуты. Сторож свежести теперь следит за ОБЕИМИ датами замеров, а не только за 31.08. Проверено: на проде из 20 сегодняшних строк витрины в полосу попадают 8 (40 %), что сходится с 35,5 % «доли в полосе» из бэктеста 12.09. Фальсификация: снятие фильтра руками красит 3 теста, ключевой — по значению ([44, 43, 41] вместо [44] на реальных строках прода +75,7 / −27,9 / +9,9 %). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 668ac40631 |
fix(tradein): подпись коридора говорит про выборку, а не про алгоритм
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m9s
CI Trade-In / backend-tests (pull_request) Successful in 5m21s
Ревью #3462 поймало ложь в микрокопии: «оценку по ним не корректировали» утверждает про АЛГОРИТМ то, чего код не гарантирует. Порог estimate_corridor_clamp_min_n гейтит только две страховки — кламп headline и radius-floor. Третий ценовой путь, гейт Tier C (#1795 шаг 3, estimator.py), сравнивает якорь с потолком коридора БЕЗ порога вообще: коридор из пяти сделок там способен уронить headline на треть (воспроизведено ревьюером: якорь Tier C 300 000 ₽/м², с коридором 200 000 против 300 500 без него). Плюс deals-headline-fallback берёт медиану коридора начиная с трёх сделок. Формулировка переписана на утверждение о ДАННЫХ — оно истинно во всех достижимых состояниях: «справочно: сделок мало (N) — коридор ориентировочный». Ветка «объявлений рядом нет» (n_analogs = 0) больше не молчит: раньше там возвращался null, и клиент не узнавал, что вся его цена стоит на трёх сделках. Теперь — «оценка построена на этих сделках — их всего N». Докстринги advisory_only в схеме и комментарий у лога тоже перестали обещать «коридор в цену не пошёл»: поле значит ровно «страховки выключены». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| b71f3f9957 |
fix(tradein): коридор ДКП с малым числом сделок помечен справочным
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m16s
CI Trade-In / backend-tests (pull_request) Successful in 5m32s
Показ коридора открывается с трёх сделок (DKP_CORRIDOR_CITY_WIDE_MIN_N), а обе ценовые страховки по нему — soft-кламп headline сверху и radius-floor снизу — включаются с десяти (estimate_corridor_clamp_min_n). В зоне n=3..9 коридор существует, показывается и участвует в fallback-путях, но цену не держит, и на экране это неотличимо от работающего коридора. PR #3445 (снятие предиката d.rooms) переносит туда реальных клиентов: замерено 248 → 3 и 72 → 8 сделок. Решение — advisory-only. Порог показа не поднят (это отняло бы у клиента информацию), кламп по трём сделкам не включён (был бы хуже своего отсутствия), но зона теперь названа вслух: - DkpCorridor.advisory_only — computed-поле от count и ЕДИНСТВЕННОГО порога estimate_corridor_clamp_min_n, так что верно во всех конструкторах коридора (POST /estimate и GET-rehydrate) и не дублирует порог вторым числом; - лог INFO с маркером corridor_advisory_zone (n, порог, scope street/city_wide, id оценки) — одна строка на оценку, считается за сутки одним grep -c; - _fetch_dkp_corridor отдаёт служебный ключ scope: «мало сделок на улице» и «мало сделок во всём городе» — разные новости, и лог обязан их различать; - на экране (v1 hero + плитка ДКП в v2) подпись «справочно: мало сделок — оценку по ним не корректировали». Подпись молчит, когда headline ПОСТРОЕН из этого же коридора (n_analogs = 0, deals-fallback): там показанная цена и есть медиана этих сделок, и подпись была бы ложью в другую сторону. Тесты по значению (test_3452_corridor_advisory_zone.py) гоняют настоящий estimate_quality с коридором, потолок которого заведомо ниже медианы аналогов: в зоне headline НЕ прижат и метка стоит, выше порога — прижат к cap и метки нет. Захардкоженный флаг в любую сторону и снятый порог клампа роняют тесты (проверено руками). Closes #3452 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
10ffa93a1c |
fix(support): доставленное сообщение не теряется при сбое БД, отказы Telegram расходуют бюджет
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m11s
CI Trade-In / backend-tests (pull_request) Successful in 5m26s
Два последних дефекта из разбора телеграм-стека, оба в ручках веб-поддержки. Предыдущие три PR (#3456, #3457, #3458) чинили клиент и мост; эти — сами ручки. ## Сбой БД уже ПОСЛЕ доставки в топик Порядок «сначала Telegram, потом БД» осознанный, но блок записи не был обёрнут ничем, в отличие от шага отправки. `SQLAlchemyError` там означал: сообщение оператору доставлено, а клиент получил 500. Дальше по цепочке — пользователь шлёт повторно, в топике дубль, а на осиротевшее зеркало оператор отвечает в пустоту, потому что треда в БД нет и мост на реплай пишет только WARNING. Обе ручки теперь ловят `SQLAlchemyError` вокруг блока БД, тихо откатывают сессию, предупреждают оператора реплаем к доставленному зеркалу и отдают клиенту успех. Успех, а не отказ: доставка правда состоялась, и отказ спровоцировал бы ровно тот дубль, которого избегаем. Анонимная ветка на этом пути дополнительно ставит куку, хотя штатно ставит её только на успехе: треда нет, но идентичность посетителя обязана пережить сбой, иначе следующее сообщение заведёт второй тред. ## Успеха мало — клиент должен об этом узнать Первая версия правки отдавала успех молча, и это было неотличимо от тишины. Фронт выбрасывает тело POST и рендерит переписку только из GET, а сообщения там нет: поле ввода очищается, в списке пусто, баннера нет. Пользователь решает, что не отправилось, и шлёт снова — тот самый дубль. Нашло adversarial-ревью, и это подтверждено чтением `useSupportChat.ts` и `SupportChatPanel.tsx`. Поэтому `SupportMessageOut` получил поле `persisted` со значением `True` по умолчанию — все существующие пути и `GET /support/messages` отдают его без изменений. На пути деградации приходит `False`, и панель показывает рядом с композером предупреждение: сообщение получено оператором, но в переписке его не будет, отправлять ещё раз не нужно. Баннер гаснет на следующей нормально записанной отправке. Анонимный виджет рендерит ту же панель и получает это поведение автоматически. Текст предупреждения оператору тоже переписан: он больше не рассчитывает на то, что клиент напишет снова, и прямо говорит, что ответить через бота не получится. ## Рейт-лимит переставал считаться при недоступном Telegram `retry_after()` — это peek, а `record()` звался только на успехе. Верно для «не наказывать за чужую аварию», но имеет обратную сторону: пока Telegram лежит, лимита нет вообще, и каждый повтор стоит до четырёх попыток к api.telegram.org, не расходуя ни один бюджет. Двух-трёх вкладок с авто-повтором хватает, чтобы выесть лимиты группы ровно тогда, когда канал и так еле жив. Добавлен отдельный счётчик отказов на тех же ключах: пять подряд в окне тридцати секунд включают cooldown, и ручка отвечает 429 не доходя до Telegram. Пять подряд на живом канале практически недостижимы, а `reset()` на успехе стирает историю — считаем именно подряд. Тридцать секунд заведомо короче реальной недоступности, так что после восстановления пользователя не наказывают. Основной «успешный» бюджет и non-destructive peek не тронуты. `SlidingWindowLimiter.reset(key)` добавлен аддитивно, с оговоркой в докстринге, что лимитерам-бюджетам он противопоказан. Барьер рассчитан на несколько вкладок с авто-повтором, а не на одиночного последовательного клиента: один отказавший запрос сам занимает до двадцати трёх секунд, и пять таких в окно не укладываются. Это принято сознательно — ловить одиночку значило бы наказывать обычного пользователя за чужую аварию. ## Тесты Отказ БД в обеих ручках: клиент получает успех с `persisted=False`, оператору уходит предупреждение, текст обращения в него не попадает, 500 не возникает. Отказ самого уведомления ручку не роняет. Серия отказов включает cooldown, и до Telegram запрос не доходит. Окончание окна cooldown снимает. Успешный путь и существующий рейт-лимит не изменились. Бэкенд: 117 passed, ruff чистый. Фронт: type-check чистый, lint без новых замечаний. |
||
|
|
a82382f47a |
feat(mera-b2c): микроразметка schema.org и картинки превью для публичного сайта
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m5s
Сайт открыт для индексации 10.09.2026, но поисковику он до сих пор
представлялся девятью страницами без единого структурированного факта о
том, кто их публикует и что продаёт, а ссылка на него в мессенджере
разворачивалась голым текстом без картинки.
МИКРОРАЗМЕТКА. Конструкторы узлов вынесены в `_lib/schema.ts` — чистый
модуль без React, по образцу соседнего `_lib/analytics.ts`. Все значения
берутся из `content.ts`, ни одной строки не продублировано:
- `Organization` + `WebSite` одним `@graph` в layout поддерева, то есть на
всех девяти страницах сразу. Остальные узлы ссылаются на организацию
через `@id`, а не повторяют реквизиты у себя.
- `Service` с `Offer` на лэндинге.
- `FAQPage` в блоке возражений.
- `BreadcrumbList` на статьях, статье, документах и странице для бизнеса —
ровно по тем крошкам, что видны на экране.
- Существующий `Article` переведён на ссылку `publisher: { "@id": ... }`.
РАЗМЕТКА НЕ ОБЕЩАЕТ ТОГО, ЧЕГО НЕТ. Два места, где это стоило внимания:
- `Offer.availability` вычисляется из `PUBLIC_ESTIMATE_ENABLED`. Флаг сейчас
`false` (платёжного контура в коде нет), поэтому в разметку уходит
`PreOrder`, а не `InStock`: цена опубликована, купить нельзя, и врать об
этом поисковику нечего. Включится приём оплаты — значение сменится само.
- `FAQPage` строится из того же массива `ITEMS`, который рисует `<details>`,
а не из `FAQ` напрямую: в `ITEMS` часть вопросов заменена макетными
формулировками, и разметка от сырого `FAQ` разошлась бы с видимым текстом
молча. Выдуманных дат, рейтингов и отзывов не добавлено нигде.
КАРТИНКИ. Две штуки, и это не дубль: `og-mera.png` 1200×630 — превью ссылки,
`logo-mera.png` 512×512 — логотип организации в разметке, который поисковик
обрезает близко к квадрату и где баннер превратился бы в обрезок надписи.
Исходники обеих лежат рядом в `scripts/og/*.html` вместе с командой
перерисовки: картинка должна оставаться правимой, а не только
переоткрываемой в графическом редакторе.
Готовые PNG, а не `opengraph-image.tsx`: satori внутри `ImageResponse` рисует
только переданными ему байтами шрифта и кириллицу по умолчанию не покрывает,
плюс не видит ни CSS Modules, ни наших `--b2c-*`. Разбор — в шапке
`scripts/og/og-mera.html`.
`images` продублирован в `twitter`: Next не переносит их из `openGraph`, когда
метаданные заданы объектом, и карточка обещала бы крупное превью без картинки.
ПЕРИМЕТР. Обе картинки лежат в `public/` и раздаются Next'ом по
basePath-корню, куда rewrite `@meraPages` не достаёт, — отсюда два отдельных
`handle` по образцу robots.txt. Оба названы в `ROOT_HANDLES_ALLOWED`, иначе
двусторонний гейт периметра покраснел бы, и это правильно: корневой `handle`
— вторая дверь в тот же периметр.
Смоук проверяет не только 200, но и Content-Type: перепутанный rewrite отдаёт
200 с HTML, обходчик мессенджера молча его отбрасывает, и по логам приложения
этого не видно — запрос туда не доходит.
Проверено: tsc, eslint, 215 тестов mera-public, изоляция B2C-дерева, caddy
validate, bash -n смоука, прод-сборка с basePath и разбор отрендеренного
HTML — JSON-LD парсится, og:image и twitter:image на месте.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ
|
||
|
|
671fef758e |
feat(mera): Метрика и GA4 на публичном контуре + открытие сайта для индексации
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m19s
CI / openapi-codegen-check (pull_request) Successful in 2m6s
CI / backend-tests (pull_request) Successful in 17m40s
ЗАЧЕМ. Статьи МЕРЫ публикуются с UTM-метками, но посмотреть, приходил ли по ним кто-нибудь, было физически нечем: веб-аналитики на публичном контуре не было вовсе. Заодно вскрылось, что «толкнуть в выдаче» тоже нельзя — всё дерево mera-public отдавало `robots: noindex, nofollow`. СЧЁТЧИКИ. Яндекс.Метрика и GA4 подключаются ТОЛЬКО в `mera-public/layout.tsx` и никогда в корневом `app/layout.tsx` — иначе счётчик уехал бы в закрытый контур (/v2, /admin, /scrapers, /history), где анонимных посетителей нет, а приватные маршруты сотрудников есть. Идентификаторы приходят build-time (`NEXT_PUBLIC_YM_ID` / `NEXT_PUBLIC_GA_ID`) — канон Dockerfile'а этого проекта: Next инлайнит NEXT_PUBLIC_* на сборке, runtime env их не подхватит. Пустое значение = тег не рендерится вовсе, никаких `ym(undefined)`. Оба build-arg'а прописаны в ОБОИХ блоках CI, включая retry-сборку без кеша. Вебвизор выключен намеренно. Он пишет ввод в поля, а на `/estimate` человек вводит адрес своей квартиры; раздел 9 политики этого не раскрывает. Включать следует одним заходом с правкой политики и маскировкой полей — в коде рядом записано, что именно понадобится. ЦЕЛИ ВОРОНКИ. Десять целей: клик по CTA, начало ввода адреса, адрес выбран, результат с разбивкой по вердикту (ok/thin/none), ошибка расчёта, ошибка валидации, отказ подсказок, показ платного тизера. Кнопок «Проверить квартиру» восемь штук в разных компонентах, все — обычные `<a>` через PublicLink, поэтому вместо восьми копий onClick один делегированный слушатель на document: девятая кнопка подключится сама. Цель «оплата успешна» НЕ заведена — вызова checkout во фронте нет вовсе, PAYMENTS_ENABLED выключен, страницы возврата не существует; вешать её пока не на что. ИНДЕКСАЦИЯ. Снят noindex со всех публичных страниц, добавлены `app/robots.ts` и `app/mera-public/sitemap.ts`, metadataBase, canonical на КОРОТКИЕ адреса, openGraph и JSON-LD Article на главной статье. robots.txt и sitemap.xml разведены по двум разным handle в Caddy не от хорошей жизни: у Next robots.txt — конвенция корня app/, а sitemap живёт в сегменте маршрута, и формы путей не совпадают. 152-ФЗ. Раздел 9 «Файлы cookie и веб-аналитика» в политике (обработчики названы поимённо — этого требует ч. 3 ст. 6) + уведомляющий, не блокирующий баннер. Гейт «названий площадок в публичной копии быть не должно» получил узкое исключение ровно на аналитические словосочетания в политике; голое «Яндекс» как площадка остаётся запрещённым и там. ПОПУТНЫЙ БАГ (замер на живом проде 10.09.2026). `meraocenka.ru/articles/` с UTM-метками отдавал 301 на адрес БЕЗ query — матчер @meraShortSlash собирал цель из regex-захвата пути и терял параметры. Код ответа при этом оставался 301, поэтому смоук проблему не видел. Мессенджеры и автолинкификаторы дописывают слэш сами, то есть атрибуция терялась именно на трафике по опубликованной ссылке. Починено тем же приёмом, что у соседних матчеров; в смоук добавлена проверка буквального Location. ГЕЙТЫ. `isPublicPath` и периметр-тест узнали про новые машинные адреса; noindex-гейт развёрнут (падает, если флаг вернулся) и расширен на строковую форму `robots: "noindex"`; заведена проверка, что корневых `handle` в site-блоке не появляется без объявления — раньше эту дверь гейт не видел. Проверено: tsc и eslint чисто, `npm run build` проходит, robots.txt и sitemap.xml отдаются по нужным адресам, при пустых ID в HTML нет ни одного обращения к mc.yandex.ru и googletagmanager, `caddy validate` валиден, изоляция mera-public от B2B не нарушена. Два теста LoginPage падают и на нетронутом дереве — не наши. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ |
||
| d953899d92 |
fix(mera): подпись тонкого рынка доезжает до карточки оценочной модели
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 17s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 2m3s
Карточка печатает тот же recommended_price, что и hero, но берёт его из своего endpoint и подписи не имела — рядом с итогом стояла неподписанная копия величины, исключённой из расчёта (ровно то, что #3339 чинил в hero). Проп note + проброс thinMarketNote(estimate.avito_imv) со страницы; подпись тем же классом .bench-chip__sub, ему же дан max-width, чтобы длинная строка не растягивала чип. Тест падает при снятии рендера подписи (проверено: 1 failed / 5 passed). |
|||
| 5356d564e6 |
fix(mera): подписать IMV на тонком рынке — число вне расчёта
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m51s
Бэкенд (#3336, thin_market) при market_count ниже порога исключает IMV из headline, но витрина ставила число рядом с итоговой ценой молча — читалось как расчётная величина. Подпись называет реальный N из market_count. Closes #3339 |
|||
|
|
76b24e9f4d |
fix(mera-public): подписи метрик снижения цены и бенчмарк отчёта не называют площадку
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m12s
CI Trade-In / backend-tests (pull_request) Successful in 5m5s
Замечание юриста (Сайт_МЕРА_v2, п.3): публичный измеримый контент не должен строиться на названных данных площадок. - landing_stats.py: note метрик price_cut_share_pct / price_cut_median_pct_per_month «Только Домклик (…)» → «Только одна из площадок (…)». Текст персистится в landing_stats.note и рендерится на лендинге в блоке «цена ошибки» — после деплоя нужен пересчёт (refresh_landing_stats), деплой кода note не перепишет. - HeroSummary.tsx / IMVBenchmark.tsx: убраны видимые «IMV» и title «Information Market Value» / «Индекс Market Value» — бренд-термин оценочной модели площадки; подпись — «оценочная модель» + sourcePublicLabel. - public-copy-no-platform-names.test.ts: «IMV», «Information Market Value» добавлены в BANNED (гейт 34/34). |
||
|
|
5541aa9d37 |
fix(mera-public): публичная копия — сервис не называет себя оценщиком, площадки-источники не названы
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 5m2s
Три юр-правки по документу владельца продукта «Сайт_МЕРА_v2» (31.08.2026), сделаны локально 31.08, но не были закоммичены — на meraocenka.ru всё оставалось по-старому. 1. «Оценщик» про собственный алгоритм. Витрина сделок (`landing_showcase_deals.py`, REJECTION_RULE/NOTE) и сноска статьи «Как оценить квартиру» теперь говорят «расчёт МЕРЫ»: самоназвание обесценивало дисклеймер «не официальный отчёт оценщика». Комментарии и докстринги не тронуты — посетитель их не видит. 2. «Путь 2»: «стоимость услуг фиксированная и известна заранее» — читалось как фиксированная цена квартиры. 3. Названия площадок убраны из видимой копии лендинга и веб-отчёта. Канон — `publicLabel` + `sourcePublicLabel()` в `source-registry.ts`: одна площадка = один номер «Источник N» и один цвет точки (цвет остаётся опознавателем между блоками), Росреестр под своим именем, неизвестный id → «Другой источник» (раньше fallback отдавал сырой id). Три параллельные реализации `sourceLabel` сведены к одной; `sourceLabel()` с реальными именами живёт для админки. Backend: якорь confidence_explanation «по оценке Avito IMV» → «по оценочной модели площадки». Подсказки геокодера «Yandex / Nominatim» и «по Яндексу» сняты из клиентских форм. Гейт `public-copy-no-platform-names.test.ts` сканирует mera-public/** и components/trade-in/** без комментариев, по Unicode-границе слова; список запретов регистрозависимый намеренно (строчные id `"avito"` законны), поэтому капс-варианты и словоформы перечислены явно — «6202 ОБЪЯВЛЕНИЯ ДОМКЛИК» в статье именно так проходил первую версию гейта. Не закрыто здесь: клиентский PDF (#3341) и ссылки на объявления на доменах площадок. Текст витрины хранится в БД (`landing_showcase_runs.rejection_rule`, `landing_showcase_deals.note`), планировщика у пересчёта нет — после деплоя нужен ручной пересчёт или UPDATE трёх подстрок на проде. |
||
| ef166eaf21 |
fix(mera-public): защиты вместо тавтологий в coverage-тексте и выборе раундов
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m9s
Проверка «уверенные + средние + пустые = 100» была верна по определению средней доли и упасть не могла. Инвариант перенесён на сам замер: уверенные и пустые обязаны оставлять место третьей доле. Если её всё-таки не останется (перезамер 85 + 15), текст теперь двухчастный — «Ещё в 0 данных меньше» отрендериться не может. `street_name !== null` считал пустую строку названием улицы, и вторая такая строка вылетала из игры как «повтор». Проверка на пустоту. |
|||
| c093212eae |
fix(mera-public): честный текст покрытия и разные квартиры в раундах игры
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m8s
Остаток «100 − уверенные» в подписи города был однородным только на словах: внутрь него входят пустые пробы, на которые describeCoverage отвечает «рядом не продаётся ничего похожего» и расчёт не предлагает. Текст обещал расчёт и на них — две наши же строки спорили друг с другом. Остаток разложен на долю «посчитаем, но разброс шире» и долю «считать не станем». Возраст объявления бэкенд считает percentile_cont(0.5) — это медиана, а «в среднем» под скошенным распределением называет другую величину. Раунды игры брались первыми тремя строками витрины, а она отсортирована по полноте и свежести: на проде это давало три студии с одной улицы. Выбор жадный по комнатности и улице, детерминированный — страница серверная. |
|||
| 5cdf2bd74c |
Merge pull request 'fix(tradein-v2): пустая выборка сделок больше не рисуется нулями — «0,00 млн · −100%» зелёным исчезает' (#3328) from fix/3320-v2-honest-zeros into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m20s
Deploy Trade-In / deploy (push) Successful in 1m1s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
|
|||
| 35db6608ee |
fix(tradein-v2): футер 04, падеж сделок и цвет дельты по знаку
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m31s
Ревью PR #3328: - HistoryView: «Показано N из M сделок» брало M из street-deals, а строки — из sales-vs-listings; после гашения пустого коридора выходило «из —». Знаменатель убран, как в 05 РЫНОК; - обе подписи склоняются через pluralRu («3 фактические сделки», «2 сделки»); - цвет дельты медианы сделок считается по знаку (mapSources.deltaColor): зелёным красились и минус, и прочерк — это второй причинный узел жалобы «−100% зелёным»; - подпись полосы совпала с карточкой 1 буквально («ПО СДЕЛКАМ РОСРЕЕСТРА»); - тест: убран тавтологичный assert про EmptyTableNote (её рисует пустой dealRows, а не guard), добавлены кейсы на футер 04 и на цвет дельты. |
|||
| 390a6f05b1 |
fix(mera-public): сверка адреса у хвоста черновика — в коде, не в комментарии
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 16s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m23s
readDraftExtras требует адрес, под который спрашивают, и на несовпадении отдаёт null. Гарантия «потребитель обязан сверить» жила только в комментарии, а takeDraft выходит раньше записи хвоста на пустом и на битом черновике — то есть хвост от предыдущей квартиры доживал до следующего захода в той же вкладке и дождался бы потребителя, который сверить забыл. Плюс два непокрытых кейса: черновик без floor/condition убирает прошлый хвост; хвост не отдаётся чужому адресу. |
|||
| 08cabe8d98 |
fix(tradein-v2): пустая выборка сделок больше не рисуется нулями
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m0s
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
Бэкенд по контракту не умеет отдать null в street-deals: «сделок на улице нет» приезжает как count=0 с нулевыми ценами. Витрина сохраняла этот ноль через ?? и показывала его как данные — «0,00 млн ₽ · −100% к цене объявления» зелёным и диапазон «0,00 – 0,00». - usableStreetDeals() схлопывает пустую оболочку в null на границе мапперов (mapSources / mapSummary / mapHistory / mapResultPanel) — дальше null уже везде означает «нет данных», отдельного состояния заводить не нужно; - футер таблицы сделок брал N и M из РАЗНЫХ выборок (строки — actual_deals оценки, знаменатель — коридор по улице), выходило «Показано 10 из 3»; тотала у той же выборки в ответе нет, поэтому знаменатель убран; - в deals-only ветке (n_analogs=0) полоса диапазона строилась из ДКП-цен, но была подписана «В ОБЪЯВЛЕНИЯХ» — подпись согласована с карточкой 1. Closes #3320 |
|||
| 7b61ecb122 |
fix(mera-public): этаж и состояние переживают переход на /estimate
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 58s
Карточка лэндинга требовала этаж и состояние обязательными и клала их в черновик. Целевая страница на монтировании звала takeDraft, а та стирала черновик целиком, прочитав только адрес, комнаты, площадь и город: оба обязательных поля уничтожались непрочитанными. Человек заполнял два поля, единственным эффектом которых было их же удаление. takeDraft по-прежнему забирает черновик «на вынос» (адрес не должен подставляться на следующей неделе в той же вкладке), но перекладывает непрочитанные floor/condition в отдельный ключ вместе с адресом, к которому они относятся, — чтобы платный шаг (#2896) не приклеил этаж одной квартиры к другой. Потребителя у полей пока нет: платного шага нет. Closes #3321 |
|||
| 012d98205f |
v2: бейдж «Мои отчёты» показывает квоту «использовано / доступно»
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 59s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
Владелец выдал покупателю 50 оценок и не смог найти, где виден остаток. Ответ был: нигде. Бэкенд отдаёт limit/used/remaining/unlimited целиком, а фронт рендерил голое used с фолбэком на длину истории — «0» у свежего аккаунта читался как «отчётов нет», а был месячным счётчиком (#3320 п.4: одна цифра с двумя смыслами). Теперь: «3 / 50» + title с расшифровкой. Фолбэк на history.length убран — lifetime-число с потолком 50 строк смыслово другое; нет квоты (unlimited или не загрузилась) — нет бейджа: лучше ничего, чем не то. Тест по значению, с открытием меню; фальсификация: возврат голого used красит «Unable to find 3 / 50». 204 теста (v2 + mera-public) зелёные. |
|||
| 2274c82b7d |
Merge branch 'main' into feat/mera-landing-attractiveness
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m9s
|
|||
| d3b0d0bea5 | Merge branch 'main' into feat/mera-hero-separate-caveats | |||
| eded14c3d6 |
Merge branch 'main' into feat/mera-landing-attractiveness
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 14s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m30s
|
|||
| e94a9792ce |
Merge branch 'main' into fix/mera-sticky-after-hero
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m7s
|
|||
| 86e6956257 |
Merge pull request 'лендинг: пятая плитка падала на вторую строку, столбец «Расхождение» читался пустым' (#3290) from fix/mera-kpi-grid-and-table into main
Some checks failed
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 14s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been cancelled
|
|||
| b277b1f678 |
Merge branch 'main' into fix/mera-sticky-after-hero
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m7s
|
|||
| 3f57171f63 |
Merge branch 'main' into fix/mera-kpi-grid-and-table
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m8s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / frontend-tests (pull_request) Has been skipped
|
|||
| 44e7d4b63a |
Merge branch 'main' into fix/mera-honest-coverage-number
All checks were successful
CI Trade-In / changes (pull_request) Successful in 17s
CI / changes (pull_request) Successful in 16s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m26s
|
|||
| 47df1a9020 |
липкая панель прячется, пока герой в кадре
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m37s
Владелец увидел на первом экране, что полоса «Проверить квартиру» лежит поверх абзаца «Мы называем реальную цену вашей квартиры». .barSpacer это НЕ лечит и не может: он добавляет высоту в КОНЕЦ документа, а .barRoot — fixed, то есть накрывает нижние ~67px вьюпорта при ЛЮБОЙ прокрутке, включая scrollTop=0. Разобравший это агент так и написал и не стал чинить молча. Пока герой виден, панель не нужна: прямо в нём стоит та же форма проверки, и полоса дублирует призыв, закрывая текст. Уходит герой — панель появляется. ПО УМОЛЧАНИЮ ПАНЕЛЬ ВИДИМА. Скрытие включает только клиентский наблюдатель; если скрипт не выполнился, поведение остаётся ровно сегодняшним. Обратный порядок (скрыта, показывает скрипт) в тех же условиях убрал бы призыв со страницы совсем — то есть чинил бы вид ценой работы. Нет секции героя — наблюдать нечего, панель остаётся видимой, а не пропадает. Убирается transform-ом, а не display:none: transform идёт на композиторе и не вызывает перекладку. Страница и так дорога в отрисовке на мобильном (1824 мс в Style & Layout), и лечение перекрытия не должно стоить пересчёта макета на каждом пересечении границы героя. Убранная панель уходит и из дерева доступности (aria-hidden + inert): иначе Tab уводит в кнопку, которой на экране нет. Тест проверяет ПОВЕДЕНИЕ, а не наличие класса в файле: наблюдателю скармливается пересечение. Фальсификация обеих сторон — панель не прячется («expected false to be true») и панель скрыта по умолчанию («до срабатывания панель видима»). 150 passed, eslint чисто. |
|||
| c4beee29ff |
для бизнеса: убраны обещания, которых нет в контуре; гейт чисел накрыл подстраницы
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 10s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m3s
Снимает два замечания ревью. 1. КОПИЯ ОБЕЩАЛА НЕСУЩЕСТВУЮЩЕЕ. «Расчёты по потоку адресов», «не для одной сделки, а потоком», «по вашему потоку адресов» — всё это подразумевает массовую загрузку, которой в контуре нет: ни CSV, ни импорта, ни пакетной ручки. Проверено grep-ом по src/app/v2. Что там ЕСТЬ на самом деле и теперь названо: учётные записи команды (app/api/v1/team.py), история своих расчётов (useEstimateHistory, оверлей «Предыдущие оценки»), месячный лимит на аккаунт (account_quota_overrides, monthly_limit/unlimited), выдача доступа вручную. Это ровно тот дефект, который на этом лендинге выкорчёвывали весь день: величина или возможность, которой никто не мерил и не делал, в продуктовом голосе. Новая страница не имеет права начинать заново. 2. ГЕЙТ ЧИСЕЛ НЕ ВИДЕЛ НОВУЮ СТРАНИЦУ. Сканер знал два места: корневой page.tsx и каталог _components/v3. Публичная /business под него не попадала — правило держалось на памяти автора. Теперь подстраницы находятся ОБХОДОМ каталога, а не списком: список — снимок, следующую страницу в него снова забудут вписать. Юридические страницы (оферта, политика, возврат, документы) исключены явно и с обоснованием: они состоят из ссылок на законы («Закон РФ от 07.02.1992 № 2300-1», «152-ФЗ», «ст. 18.1»), а по форме это те же числа, что и замеры. Отличить регулярным выражением нельзя, и правило гейта к ним не относится — там числа цитируются из нормативных актов, а не утверждаются о рынке. Расширение проверено: включение этих страниц давало 4 ложных срабатывания. Фальсификация: вписанные в /business «42 700 квартир» роняют гейт по значению с указанием файла. 150 passed, tsc и eslint чисто. |
|||
| 79ae87019a |
бэктест: подписи окна приведены к замеру, уверенность — к тому же прогону
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m21s
Снимает три блокера ревью на этой же ветке.
1. ПОДПИСЬ ОКНА. Числа перевели на представительную выборку, а подпись
осталась от прежнего прогона: «сделки II квартала 2026 года». Автор честно
записал, что квартальный состав пересборки не смотрел, и оставил критерий
снятия — посчитать deal_date по выборке. Посчитал:
III кв 2025 — 106 (26,5 %) I кв 2026 — 88 (22,0 %)
IV кв 2025 — 110 (27,5 %) II кв 2026 — 96 (24,0 %)
Пересборка покрывает ЧЕТЫРЕ квартала, II квартал 2026 — четверть её. Прежняя
подпись для этих чисел была бы прямым враньём.
2. ЗНАМЕНАТЕЛЬ. Был 5 954 — сделки только II квартала. Для выборки по четырём
кварталам это завышало долю вчетверо: 5,5 % вместо 1,3 %. Замер тем же
фильтром, что у выборки: в окне 24 333, из них II квартал 2026 — 5 954. Оба
числа одним запросом.
3. УВЕРЕННОСТЬ. Плитка «325 из 327» была из прогона 29.08, а лид секции печатал
«замер 31.08» — старому числу приписывалась новая дата, и 325 в лиде совпадало
с 325 в плитке, читаясь как одна выборка. Заменено на «400 из 400» из ТОЙ ЖЕ
пересборки. Знаменатель 400 — запрошенные сделки, а не 325 сматчившихся:
уверенность считается на каждой оценке.
4. Оговорка уверенности не выводилась вовсе: AccuracyV3 собирал оговорки двух
величин из трёх. Плитка стояла без единого слова о том, почему уверенность
низкая. Добавлена третья.
Тесты проверены фальсификацией: возврат знаменатели одного квартала даёт
«expected 5954 to be 24333» и «expected 5.458 to be less than 2»; снятие
оговорки из вывода — «оговорка не выводится». 157 passed.
|
|||
| 2864d80854 |
fix(mera-public): честные числа бэктеста — медиана трёх представительных пересборок
Покрытие 88 % и расхождение 14,5 % получались только на кластеризованной выборке (ORDER BY id DESC: 20 строк на четыре улицы). Замер 31.08.2026 тремя представительными пересборками (engine=full, ЕКБ, три соли) дал покрытие 82,77/84,31/86,02 % и MAPE 14,66/15,28/15,66 %: опубликованные 88 % лежат ВЫШЕ всего диапазона, то есть недостижимы, а 14,5 % — чуть ниже, тот же благоприятный край. - BACKTEST.coverage 88 % -> 84,3 %, priceError 14,5 % -> 15,3 % (медианы), sampleN 327 -> 325 (медианная пересборка); - в оговорки добавлен сам диапазон пересборок (82,8-86,0 % и 14,7-15,7 %): точечное значение без него обещает точность, которой у замера нет; - BACKTEST_SOURCE переписан — представительная выборка, а не последние загруженные строки; дата замера 29.08 -> 31.08.2026; - confidenceLow («325 из 327») на пересборках НЕ пересчитывался: у него отдельный источник с прежней датой и оговорка про свой знаменатель; - квартальный состав представительной выборки не проверен — записан дословно, с критерием снятия, рядом с BACKTEST_PERIOD_LABEL. Тест: backtest-freshness проверяет наличие диапазона в оговорках (+контроль на инструмент) и что AccuracyV3/HeroV3 не содержат самих величин; render-тест перестал сверяться с вписанным «14,5 %» и берёт значение из landing-facts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 4d51d19f7c |
feat(mera-public): публичная страница /business вместо ссылки на форму входа
Кнопка «Для бизнеса» в шапке и «МЕРА для бизнеса» в подвале вели на B2B_URL. Реальная цепочка на проде: 308 → /trade-in → 307 → /trade-in/v2 → клиентский редирект → /trade-in/login. Посетитель публичного сайта упирался в форму «Логин / Пароль» от аккаунта, которого у него нет. Заведена страница /business: кому полезен бизнес-контур, чем он отличается от бесплатной проверки, и как связаться (почта и телеграм — те же константы, что в подвале и в документах, не выдуманные для страницы). Ни цен, ни сроков, ни объёмов: этих величин никто не мерил. Ссылка на вход оставлена внутри страницы второстепенной — «уже работаете с нами». Периметр согласован во всех четырёх местах: PUBLIC_ROUTES, три матчера Caddy (@meraPages, @meraShortSlash, @meraLongPages), PUBLIC_SHORT_PATHS в RouteGuard, плюс строка в smoke-mera-perimeter.sh. Гейт периметра проверен фальсификацией: удаление /business из @meraShortSlash роняет public-perimeter.test.ts. noindex — тем же способом, что у /docs (metadata.robots). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| c9251d3ed5 |
первый экран: оговорки вынесены из блока преимуществ
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m0s
Владелец попросил сделать лендинг привлекательнее. Один скриншот мобильной страницы показал причину, по которой первый экран читается стеной текста: шесть однотипных пунктов подряд, из которых ДВА про обратное. «Пример по рынку Екатеринбурга, а не расчёт по вашему адресу» и «По всем расчётам МЕРЫ, а не только по Екатеринбургу» лежали ВНУТРИ .heroPerks — в том же контейнере, тем же кеглем, с маркером в том же ряду, что «Бесплатно» и «За 150 ₽». То есть дисклеймер выглядел как ещё один пункт оффера. Вынесены в собственный блок за разделителем: тише, мельче, отдельно. Слова НЕ ТРОНУТЫ. Порядок чтения не тронут: оговорки по-прежнему выше формы, человек прочитает их до ввода адреса — ровно то требование, ради которого их туда и ставили. Ширина колонки та же, контраст держит AA. Убирать или прятать оговорку нельзя: замаскированная оговорка хуже показанной, и вся эта страница построена на обратном принципе. Тесты, проверяющие СОДЕРЖИМОЕ обеих строк (что экспозиция и аналоги подписаны разными границами и не слиты в одну), держатся за parentElement строки, а не за контейнер — переезд их не задел. 147 passed. |
|||
| 2527068c45 |
подача лендинга: мобильный ритм, ровные заголовки, табличные цифры
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 15s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m15s
Только оформление: ни одного утверждения, числа или секции не тронуто. - text-wrap: balance на заголовках секций — на 375px они уходят в 2-3 строки и оставляли висячее слово. - artTitle подтянут к общему полу 26px (был 24px — последняя секция читалась мельче остальных). - внутренние отступы карточек стали responsive (clamp): на 375px под текст оставалось 279px из 375, четверть экрана уходила в поля. На десктопе значения прежние. - font-variant-numeric: tabular-nums на крупных значениях (плитки сверки, плитки цены ошибки, отклонение в строке) — разряды встают в колонку. - «Все статьи» имела высоту нажатия 23px; паддинг гасится отрицательным полем, макет не сдвигается. Ховер переведён с нижней границы на подчёркивание, иначе линия легла бы по краю паддинга. |
|||
| 28bf3a82d7 |
fix(mera-public): плитки точности в один ряд, «Расхождение» по одному краю
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m21s
Пятая плитка (тёмная, из /stats) не влезала в грид: контейнер 1168px, minmax(220px) + gap 20px требуют 1180px — плитка падала на вторую строку одна и узкая, читалось как обрыв вёрстки. Заменил грид на flex-wrap с общим базисом 200px: пять плиток встают в ряд, четыре — тоже, а на узких ширинах остаток растягивается на всю строку вместо огрызка слева. Значения столбца «Расхождение» стояли по левому краю ячейки, а заголовок — по правому, из-за чего столбец читался пустым. Выровнял значения вправо, по краю заголовка; на мобильном брейке text-align по-прежнему сбрасывается в left (карточки с data-label не тронуты). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 3b09368669 |
контраст: два оставшихся нарушения на живой странице + сторож по секциям
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m0s
Проверка axe-правилом по ОТРЕНДЕРЕННЫМ узлам прода дала 32 → 2. Обе оставшиеся показательны, и одну из них завёл мой же вчерашний батч. .tickerNote (3.06) — подпись под тикером. Её добавила одна ветка, токены чинила другая; каждая по отдельности была права, вместе дали дефект: подпись взяла --b2c-muted (токен для СВЕТЛОГО фона) и легла на тёмную плашку #121A1D, где шкала перевёрнута. Красится --b2c-muted-dark, 6.20. .stepsLabelAccent (4.48) — «ШАГ 3 · 150 ₽» на карточке #EAF5F7. Сторож знал эту поверхность, но проверял accentDeep только как ЗАЛИВКУ (белый по нему), а CSS использует его ещё и как ЦВЕТ ТЕКСТА. accentDeep #0C7B8B → #0B7382: минимальный шаг, проходит текстом на всех трёх светлых поверхностях (4.99/5.15/5.54) и заодно улучшает главную кнопку — белый по нему 4.97 → 5.54. Две правки сторожа, обе про механизм, а не про случай: 1. accentDeep проверяется и как текст: токен, используемый двумя способами, обязан проверяться обоими. 2. Новый тест «в тёмных секциях не используются светлофоновые токены». Он ловит промах в ВЫБОРЕ токена, а не в его значении — тот класс, который и прошёл мимо. Список тёмных секций явный, но это снимок СЕКЦИЙ, а не нарушений: новое правило внутри известной секции ловится без правки списка. Фальсификация обеих: возврат accentDeep даёт «4.4775 >= 4.5», возврат .tickerNote на muted даёт «.tickerNote → --b2c-muted». Первую попытку фальсификации засчитывать было нельзя — подмена не сработала из-за добавленного комментария, и тест остался зелёным по построению. |
|||
| f3e05e1907 |
Merge branch 'main' into fix/mera-keep-floor-in-draft
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 16s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 2m21s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
|
|||
| 619e38b1e7 |
Merge branch 'main' into fix/mera-ticker-context
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m15s
|
|||
| be59ca4452 |
Merge branch 'main' into fix/mera-public-contrast
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m20s
|
|||
| df53ff5a5f |
лендинг: этаж перестал теряться на переходе к проверке
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m5s
Этаж — обязательное поле карточки: без него форма не отправляется. При этом
saveDraft его не клал, а на целевой странице поля этажа нет вовсе. Комментарий
объяснял это тем, что «повторно набирать одно короткое поле дешевле, чем
хранить лишнее» — довод неверен: переспросить негде, и обязательное поле
работало чистой помехой.
Схема бэкенда этаж принимает (TradeInEstimateInput.floor), и на цену он влияет
(первый и последний этаж). Хранится теперь по тому же доводу, что и состояние:
выбросить уже полученный ответ и спросить второй раз хуже, чем донести.
Тест — через САМУ ФОРМУ, и это не формальность. Первая попытка защитить правку
звала saveDraft({floor}) напрямую: она проверяла round-trip хранилища и
оставалась зелёной, когда из FreeCheckCard убирали передачу этажа, то есть
ровно при возврате чинимого дефекта. Сторож, который не сторожит. Теперь форма
заполняется и отправляется целиком; снятие проводки даёт «expected undefined to
be 7/16» — красное по значению.
Плюс три случая на само хранилище: старый черновик без поля переживает выкатку,
нестроковое значение выпадает не унося остальное.
|
|||
| 8aac93ee9b |
fix(mera-public): контраст WCAG AA на публичном лендинге
All checks were successful
CI Trade-In / changes (pull_request) Successful in 14s
CI / changes (pull_request) Successful in 17s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 2m3s
axe 30.08 нашёл 32 узла color-contrast (все serious). Причина одна на все: пары «цвет × фон» подбирались глазами, а числа в комментариях писались по памяти — шапка b2c-tokens.ts утверждала «onAccent на accentDeep 4.6:1», тогда как реальный замер 4.41, то есть главная кнопка призыва не проходила норму, а документация уверяла, что проходит. Затемнены четыре токена до запаса на худшем светлом фоне (surfaceSoft, он темнее pageBg): accentDeep 4.41→4.97, muted 4.45→5.36, success 4.04→4.96, danger 4.60→5.23. Оттенок сохранён, фирменный вид не меняется. Отдельный класс — светлый токен на тёмной плашке surfaceDark, где шкала перевёрнута: метки тикера красились muted (2.7) и accentText (2.85), стрелка между прогнозом и фактом — линейным #2c3a3f (1.50). Переведены на mutedDark (6.2) и accent (6.24). Заодно убраны сырые hex #9fb0b4 (2.24 на светлом — .artSoon, .gameScale, .repPaidFine, .artFootnote; 7.84 на тёмном) — палитра запрещает hex в CSS именно потому, что мимо токена величина не считается никаким аудитом. Регресс ловится без браузера: contrast-tokens.test.ts считает яркость по формуле WCAG прямо на значениях токенов и проверяет ОБА фона страницы, плюс сторож на новые сырые hex в CSS. Проверен фальсификацией: возврат muted к #667579 красит тест. |
|||
| 7d6caed5fb |
МЕРА: лента над первым экраном получает контекст разброса
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 19s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m45s
Над лентой стояло «медианное расхождение с ценой ДКП — 14,5 % по 327 сделкам», а в самой ленте 5 строк из 20 расходились больше чем на 30 % (75,7 · 63,6 · 47,4 · 41,4 · 38,5). Первое, что видел посетитель страницы про точность, — крупный промах без единой цифры контекста. Промахи не прячутся: строки витрины отобраны по ПОЛНОТЕ и СВЕЖЕСТИ (_sort_key в app/tasks/landing_showcase_deals.py), отбор по величине ошибки был дефектом и снят. Лечится контекстом — под лентой печатается тот же разброс ПОКАЗАННЫХ строк, что уже стоит под таблицей сверок: медиана модуля и худшая, обе из shownSpread() (deal-view.ts), второго расчёта не заводится. Вывода вида «зато обычно точно» в подписи нет: он протух бы на первом пересчёте витрины, а два числа рядом — нет. Разметка: бегущая часть выделена в .tickerStrip — position/overflow нужны только ей, иначе абсолютный бейдж растянулся бы и на подпись. Проверка (landing-v3-render.test.tsx): те же строки с худшей и без неё дают РАЗНЫЕ числа в подписи — она сосчитана по показанному, а не вписана. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 00f177df03 |
лендинг: имя боевого сервера убрано из пользовательской копии
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 58s
«Симуляция пробы покрытия на боевой базе (poincare, 30.08.2026)» доезжала до экрана: CITY_COVERAGE_SOURCE собирается в coverage-copy.ts и рендерится в EstimateFlow.tsx:323. На первом экране лендинга строки не видно — она появляется в форме проверки, после выбора города, поэтому grep по отрендеренной главной давал ноль и дефект выглядел несуществующим. Хостнейм посетителю не сообщает ничего: «на боевой базе» несёт весь смысл. Убран из обеих строк-источников (сверка бэктеста и проба покрытия) и из комментария в статьях. Гейт смотрит в ИСХОДНИК, а не в вывод: какой путь рендера показывает строку сегодня, знать не нужно — завтра он будет другим. Список запретов явный, без эвристики «похоже на хостнейм»: она ловила бы domclick.ru и avito.ru в текстах про источники данных. gendsgn.ru в список НЕ входит — это публичный домен B2B, на который лендинг ссылается кнопкой «Для бизнеса». Гейт проверен фальсификацией: возврат хостнейма даёт красное с указанием landing-facts.ts:207, а не пустой список. |
|||
| 5f7ac0d50f |
витрина: печатать разброс показанных строк рядом с медианой всей сверки
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m18s
Посетитель видел 20 строк сверки и не мог понять, где они лежат относительно выборки: первой в таблице стоит сделка с расхождением +75,7 %, а медиана по всем 327 — 14,5 %. Порядок строк при этом верный и менять его нельзя — отбор идёт по полноте и свежести, а не по величине ошибки (отбор по ошибке был дефектом и уже убран). Поэтому не переупорядочиваем, а добавляем контекст: медиана модуля расхождения показанных строк, сколько из них в пределах порога и худшая — рядом с медианой всей сверки. Замер на проде 30.08.2026: показанные 11,5 % против 14,5 % по всей выборке, 12 из 20 в пределах 20 %, худшая 75,7 %. Считается на фронте из тех же объектов, которые рисует таблица, — второе место подсчёта рано или поздно отстало бы от строк на экране. Своей формулировки «чуть точнее» в подписи нет: она протухнет на первом же пересчёте витрины, а два числа рядом не протухают. Порог вынесен в WITHIN_PCT рядом с фильтром: гейт витринных чисел справедливо покраснел на вписанных руками «20 %» при <= 20 в коде. |
|||
| 79fc8c45ff |
feat(mera/estimate): предзаполнять этажность, год и тип дома из справочника (UI) (#3259)
Some checks failed
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m5s
Deploy Trade-In / test (push) Successful in 4m9s
Deploy Trade-In / build-backend (push) Has been cancelled
|