259 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 66660bd366 |
fix(mera/b2c): на мобильном липкий хром съедал 42% экрана
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 / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m38s
CI / openapi-codegen-check (pull_request) Successful in 2m26s
CI Trade-In / backend-tests (pull_request) Successful in 5m28s
CI / backend-tests (pull_request) Successful in 17m44s
Жалоба «много всплывашек, экрана не видно» — это не всплывашки, а постоянный
хром: на 375×812 липкая шапка (235 px) и фиксированная нижняя панель (109 px)
вместе занимали 42% экрана и отъедали их при любой прокрутке. Замер тем же
способом (getBoundingClientRect по элементам с position fixed/sticky): было
366 px = 45,1% высоты экрана, стало 119,5 px = 14,7%. На 1280 — 135,5 px до и
после, без изменений.
Шапка на узком экране сведена к одной строке (лого + «Для бизнеса»):
- навигация по секциям скрыта — четыре пункта переносились в две строки, а
на мобильном к секциям всё равно доезжают прокруткой; «Статьи» и «МЕРА для
бизнеса» остаются ссылками в подвале, «Продажа под ключ» и на десктопе
неактивная заглушка;
- CTA скрыт — он вёл туда же, куда нижняя панель, и два одинаковых призыва
на одном экране это и есть жалоба; панель осталась, шапка уступила.
Селектор города (CityPicker) удалён совсем, а не спрятан: он не владел своим
выбором — состояние жило внутри компонента и никуда не отправлялось, — тогда
как гео-гейт расчёта стоит на городе из формы (FreeCheckCard, #2576). Два
места, задающих одно значение, из которых работает одно, — дефект сам по себе;
то же правило уже записано в InnerHeader. Граница покрытия, ради которой
выпадашку открывали, стоит текстом в герое.
Нижняя панель на узком экране — только кнопка: её строка переносилась на две-
три и делала панель втрое выше кнопки. То же обещание остаётся словами героя.
Шаг медиазапроса — 1000 px, а не общий для файла 720: на 720 шапка макета всё
ещё переносится в две строки (замерено: 109 px хрома), то есть возвращать её
там значило бы вернуть тот же дефект планшету.
|
|||
| a5ca2a326a |
feat(mera/b2c): в карточке игры схема улицы сделки, а не пятно района
Карта показывала ПОЛИГОН РАЙОНА — десятки квадратных километров, на которых одинаково выглядят сделка у парка и сделка у ТЭЦ. Схема улиц уже приезжает строкой витрины (`street_scheme`), рисовать её было нечем. `StreetMapV3` — серверный компонент, инлайновый <svg>, НИ ОДНОГО внешнего запроса: тайлы отдали бы провайдеру IP посетителя и то, что он смотрит, на странице, которая обещает «без звонков и регистрации» (правило шапки layout.tsx, по нему же в дереве self-hosted шрифты). Слои снизу вверх: плашка и приглушённая сетка (уже были), вода, фоновые дороги толщиной по классу OSM, целевая улица акцентом поверх мягкого ореола, подписи соседних улиц моноширинным. DealMapV3 ОСТАЁТСЯ и не тронут. Улица матчится у 92.1% строк витрины, у остальных схемы нет — там по-прежнему район. Ветвление стоит в GuessGameV3, чтобы обе карты остались тупыми: каждая рисует то, что ей дали, и ни одна не подставляет вместо отсутствующих данных правдоподобное. ЧТО КАРТА ГОВОРИТ О СЕБЕ. Подпись — «<УЛИЦА> · ДОМ НЕ ИЗВЕСТЕН». Адрес сделки уровня улицы, номер дома известен у 2.7% сделок, координата окна — центроид улицы. Слов «объект» и «адрес» в карточке нет; тест держит и то и другое. Точку дома поставить нечем и по построению: в `street_scheme` нет ни констант проекции, ни координат окна. ЧЕГО НЕ РИСУЕМ. Зданий: `cad_buildings` — 18 307 контуров на город, в плотном центре 51 здание на радиус 450 м, где их в разы больше; нарисованная застройка заявляла бы полноту, которой нет. Улицы — фильтрованная выгрузка «источников шума», дворовых и служебных проездов в ней нет, поэтому фоновые дороги приглушены: это контекст, а не план квартала. КАДР — `slice`, а не `meet`: окно квадратное, карточка нет, и `meet` оставил бы поля по бокам. Обрезка безопасна ровно потому, что целевая улица лежит в ЦЕНТРЕ окна по построению; обрезаются края с частью подписей соседей. ДОСТУПНОСТЬ — как у DealMapV3, `aria-hidden`: всё, что схема сообщает, стоит рядом текстом. `role="img"` заставил бы прочитать то же самое дважды, а названия соседних улиц без их взаимного расположения ничего не значат. ЦВЕТА — токенами: `--b2c-water` (вода отдельным оттенком, иначе склеивается с дорогами) и `--b2c-muted-dark` (подписи на тёмной плашке: `muted` даёт там 3.2:1). Литералов в CSS не добавлено. Подпись объекта лежит ПОВЕРХ карты, и карта теперь доходит до краёв — добавлено затемнение под ней и плашка под подписью карты; без них белый текст пересекался с подсвеченной улицей. Псевдоэлемент позиционирован, поэтому детям `.gameMapBottom` задан `position: relative` — иначе затемнение красится поверх текста и стирает его (поймано скриншотом, а не рассуждением). Тесты (каждый сломан вручную и покраснел): кадр из данных и `slice`; ореол шире линии и по одному на путь; иерархия дорог и незнакомый класс не дают нулевой толщины; вода отделена; подпись называет улицу и говорит про дом; `aria-hidden`; ветвление карточки в обе стороны. Проверено глазами на dev-сервере, стаб — реальные строки прода плюс схемы, собранные той же проекцией по геометрии gendesign: улица сматчилась; не сматчилась (виден район); полей схемы нет вовсе (тот же район, без падения). |
|||
| 4e3279d448 |
fix(mera/b2c): студия подписывалась как «0-к» на витрине сделок
Увидел на скриншоте карточки игры: «0-к, 25,9 м²». Замер на проде — 3 строки витрины из 20 имеют rooms = 0, то есть каждая шестая карточка так и выглядит. «0-к» читается как ошибка выгрузки, а не как тип квартиры. Студией её называет и наш собственный бэктест (per_rooms.label в scripts/backtest_estimator.py), и рынок. Тест двусторонний и фальсифицирован: возврат «0-к» для rooms=0 красит его по значению, обратная правка — снова зелено. |
|||
| 272d807f55 |
feat(mera/b2c): в карточке игры настоящая карта вместо декорации — и подпись, которую данные выдерживают
Блок `gameMap` рисовал CSS-сетку и два прямоугольника-«дороги», а подпись говорила «ОБЪЕКТ · РАЙОН» — то есть заявляла местоположение объекта. Географии там не было вовсе. Что сделано: `DealMapV3` — инлайновый <svg> по статике `ekb-districts.ts`, без единого внешнего запроса (то же правило, по которому в этом дереве self-hosted шрифты). Район сделки — заливка и обводка акцентом, соседние — контур; кадр по bbox района с запасом, не по городу: в городском кадре район занимает несколько процентов площади. Точка — по `project(lon, lat)` координаты витрины. ПОДПИСЬ. Координата в данных — ЦЕНТРОИД УЛИЦЫ, не дом: 34 021 сделка, 34 017 с координатой, различных точек 991, номер дома известен у 2.7% (прод, 29.08.2026). Поэтому карта подписана «СЕРЕДИНА УЛИЦЫ, НЕ ДОМ · РАЙОН» — она произносит границу вслух, а не оставляет читателю догадываться по размеру маркера. Слова «объект» и «адрес» в карточке не появляются. ТОЧКИ НЕТ — ДВА СЛУЧАЯ, оба дают карту с подсвеченным районом и без прочерка: координаты нет; координата есть, но лежит вне кадра. Второе не теоретическое — 2 132 строки из 34 021 (6.3%) в той же выборке имеют координату за пределами города, встречаются точки за сотни километров. Прижать такую к краю значило бы показать место, которого в данных нет; подпись в обоих случаях — «КООРДИНАТЫ НЕТ». Доступность: <svg> aria-hidden, всё его содержание (район) стоит рядом текстом дважды — в подписи карты и в мете сделки. Гейт витринных чисел: из скана исключён сгенерированный `ekb-districts.ts` — координаты полигонов не утверждают ничего о рынке. Исключение не бланковое: добавлена проверка, что исключённый файл несёт маркер генератора. Проверено глазами на dev-сервере с заглушкой бэкенда: три состояния (Кировский с точкой, Чкаловский — кадр едет, сделка без координаты). |
|||
| 7b12b07935 |
feat(mera/b2c): границы районов ЕКБ статикой — основа настоящей карты в игре
Карточка игры «Сыграйте против МЕРЫ» показывает декоративный прямоугольник: сетка и две «дороги», нарисованные CSS. Заменяем настоящей геометрией. ПОЧЕМУ СТАТИКА, А НЕ ТАЙЛЫ. Публичное дерево mera-public держится без единого внешнего запроса — шрифты self-hosted, Leaflet с unpkg сюда намеренно не тянули (шапка layout.tsx). Тайловый провайдер получал бы IP посетителя и то, что он смотрит, на странице, которая обещает «без звонков и регистрации». Границы районов не меняются годами, раздавать их в рантайме незачем. Что в коммите: · backend/scripts/export_ekb_districts_svg.py — генератор. У данных обязано быть проверяемое происхождение: источник — боевая gendesign_ekb_districts_geom (OSM через FDW, читается с 29.08 — грант #3227), допуск упрощения и проекция названы в докстринге вместе с причиной выбора. · _components/v3/ekb-districts.ts — сгенерированное, руками не править. 8 районов, 7826 символов путей, плюс project() для точки (lon,lat). Упрощение 0.0012° (~70-130 м) выбрано под масштаб карточки — она показывает район целиком, где такая ошибка меньше толщины линии. |
|||
| 2e82b0810f |
fix(mera/b2c): снять с витрины обещание времени и подписать величины измеренным
All checks were successful
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 Trade-In / frontend-checks (pull_request) Successful in 57s
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / openapi-codegen-check (pull_request) Has been skipped
«30 секунд» ушло из бейджа шага 1 как незамеренное, но осталось видимым посетителю в четырёх других местах той же страницы: подпись под кнопкой формы, CTA шапки, CTA блока возражений и липкая нижняя панель, которая висит на экране всегда. Гейт витринных чисел этого не видел по построению — «сек» не было среди единиц, а трёх файлов не было в списке сканируемых (обе дыры закрыты отдельно). Замена — по смыслу: число полей формы (STEP1_FIELDS_LABEL) проверяется глазами, и оба места берут его из landing-facts.ts, а не пишут у себя. Медиана аналогов стояла под общей шапкой «пример по рынку Екатеринбурга», но городом не ограничена: _ANALOGS_SQL идёт по всей таблице trade_in_estimates, фильтр по city стоит только у экспозиции. Плюс Hero печатал у неё лишь размер выборки и выбрасывал note «только расчёты, где аналоги нашлись» — отбор выживших. Теперь у каждой величины своя подпись со своей границей и своя оговорка рядом со значением; правило зафиксировано двумя проверками рендера (обе краснеют на возврате к одной строке и на выброшенном note). price_cut_share_pct считает долю ОБЪЯВЛЕНИЙ и только по Домклику — подпись «столько продавцов снижают цену» приписывала величине единицу, которой её не мерили. Один продавец ведёт несколько объявлений. В ReportResult оставались три литерала «150 ₽» при живой SERVICE_PRICE_RUB: цена обязана иметь один источник с офертой и политикой возврата. Обещание «прогноз срока продажи» приведено к тому, что считается: _estimate_days_on_market — медиана days_on_market аналогов, то есть сколько похожие объявления УЖЕ висят. deals.days_on_market заполнен 0 раз из 108 623, сверять прогноз срока не с чем (по этой же причине в ветке уже снято «±6 дн»). |
|||
| b17a2a310c |
fix(mera/b2c): гейт витринных чисел ловит происхождение, а не форму записи
Ревьюер нашёл три дыры и воспроизвёл каждую на живом коде.
1. Сканер проверял ФОРМУ. Четыре маркетинговых утверждения, вписанные в
DealsTickerV3, оставляли гейт зелёным 17/17: «9 из 10» (доля словами),
«42700 квартир» (разделитель тысяч никакой + единица вне списка),
«в 2 раза точнее» (кратность словами), «42 городах». Последнее — строка
из таблицы расхождений СТАРОГО гейта, то есть новый пропускал ровно тот
класс вранья, ради которого заводился. Добавлены четыре формы; эти же
четыре строки внесены в контроль на инструмент — прежние шесть образцов
повторяли то, что сканер и так знал, и покрытия не расширяли.
2. noindex перестал охраняться. `toContain("robots")` остаётся зелёным при
`index: true` — проверено переворотом флага. Вернулась проверка значения
(не слабее прежней `noindexIsOn()`), и не только в layout, но и в
метаданных самой страницы, которые layout перекрывают.
3. Список файлов отставал от страницы: корень рендерил 15 компонентов,
сканировались 11 — «возражения», шапка, подвал и sticky-CTA были вне
охраны. Список больше не пишется руками: он выводится из каталога
компонентов, а отдельная проверка требует, чтобы всё, что импортирует
корень, попало в этот список (ловит компонент, положенный мимо каталога).
Гейт на этой ветке КРАСНЫЙ и красный по делу: расширенный список нашёл
«Проверить за 30 секунд» в HeaderV3, StickyCtaV3, ObjectionsV3 и
FreeCheckCard. Замера времени заполнения формы нет и не было — это записано
в landing-facts.ts и в StepsV3, откуда ту же фразу уже сняли. Правка текста —
вне участка этой задачи (файлы вёрстки правит соседняя ветка).
|
|||
| 2c9b4ab547 |
feat(mera-public): витрина лэндинга v3 на замеренных числах
Литералы из дизайн-макета заменены величинами из /stats и /showcase, а то, чему нет входа в данных, снято вместе с блоком. Что заменено: - ошибка «1,8%» → 14,5% (n=327) с двумя оговорками рядом: сравнение идёт с объявлениями, активными на момент расчёта, а сделка прошлая; цена ДКП бывает занижена ради налога; - «83% в диапазон» → 88% (n=327) неразрывно с шириной коридора ±37% в подписи и с третьей плиткой: уверенность «низкая» у 325 из 327; - «±6 дн. точность по сроку» СНЯТА — deals.days_on_market пуст 0/108 623; на её месте медианная экспозиция активного объявления, без ± и без «точности»; - «42 700 проверок, из них 3 180 сверены» → estimates_total; «сверено» снято (контура сверки прогноза со сделкой клиента нет); - «−4,3% за месяц» → price_cut_median_pct_per_month (среди снижавших, своя выборка); «×2,4 дольше» СНЯТА (замер даёт ×1,04) → price_cut_share_pct; - «14 объявлений / 47 дн.» → analogs_median и listing_age_median_days, прямо помеченные как пример по рынку, а не расчёт по адресу посетителя; - 5 выдуманных строк сверок и 3 раунда игры → реальные сделки /showcase (район+комнаты+площадь+этаж, квартал вместо дня) с подписью прогона; - цена во всех местах — SERVICE_PRICE_RUB из content.ts. Отказ ручки стоит блока: пустой /stats снимает плитку, пустой /showcase — таблицу, ленту и игру. Ни нуля, ни прочерка, ни прошлого значения. Гейт #2904 переписан: он охранял «плейсхолдеры существуют» и покраснел бы по построению за их снятие. Новый охраняет происхождение чисел — сканирует компоненты на вписанные руками величины и проверяет заполненность записей landing-facts.ts. Фальсифицирован: вписанное в AccuracyV3 «1,8% по 42 700 проверкам» роняет гейт тремя формами сразу. Сканер прикрыт контролем на образцах — он же поймал уже существовавшую «150 ₽» в FreeResultV3. robots/noindex не тронуты. |
|||
| f612401202 |
feat(mera-public): серверный слой витринных чисел лэндинга
Загрузка /api/public/mera/stats и /showcase для серверных секций лэндинга: типы обеих ручек, честная деградация и форматирование величины вместе с размером выборки. Любой сбой (сервис недоступен, 500, не-JSON, пустой ответ, нет ключа) даёт «величины нет», а не ноль: /stats отдаёт пустой набор, /showcase — null, и ни одна из функций не бросает, чтобы отказ ручки стоил блока, а не всей страницы. Число выходит наружу только через formatStat — вместе с sample_n и note, поэтому потерять размер выборки по дороге в JSX нельзя. Адрес серверного fetch — BACKEND_URL, тот же путь, которым Next уже ходит в backend (rewrites в next.config.ts, 'internal SSR' в prod-компоуз), поэтому без /trade-in: префикс добавляет Caddy для браузера, не бэкенд. Кэш — revalidate 3600: пересчёт ночной, no-store жёг бы rate-limit ручки на всех посетителей, кэш без срока показывал бы вчерашнее до деплоя. Тесты двусторонние и фальсифицированы: подстановка нуля вместо null роняет 5 из 17, пустая витрина вместо null — 3 из 17. |
|||
| 5d93f13030 |
Merge pull request 'feat(mera/b2c): новый дизайн становится корнем meraocenka.ru, функционал старого перенесён' (#3236) from feat/b2c-v3-becomes-root into main
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 Infra Host / sync-infra-host (push) Successful in 7s
Deploy / changes (push) Successful in 8s
Deploy Trade-In / changes (push) Successful in 13s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 43s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-worker (push) Successful in 44s
Deploy / build-frontend (push) Successful in 43s
Deploy / deploy (push) Successful in 1m36s
Deploy / deploy-status (push) Successful in 1s
Deploy Trade-In / build-frontend (push) Successful in 2m27s
Deploy / perimeter-smoke (push) Failing after 14s
Deploy Trade-In / test (push) Successful in 4m20s
Deploy Trade-In / build-backend (push) Has been cancelled
|
|||
| ac2633011b |
fix(mera/v2): форма принимала этаж больше этажности дома (#3226)
Some checks failed
Deploy Trade-In / changes (push) Successful in 12s
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 2m15s
Deploy Trade-In / deploy (push) Successful in 1m3s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Failing after 15s
|
|||
| 49652f4413 |
fix(mera/b2c): вернуть потерянные вопросы FAQ, починить адрес превью и честность двух комментариев
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 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
Правки по трём ревью ветки «v3 становится корнем».
FAQ. Из шести вопросов `content.ts` возражения v3 брали два, остальные
четыре (`how-accurate`, `vs-marketplace`, `personal-data`, `bank-report`)
не рендерились нигде: `Faq.tsx`, который выводил все шесть, удалён вместе
с лэндингом v1. Макетные возражения их тематически закрывали, но
пересказом — а оригиналы проходили юр-ревью. Содержательно пропали два
утверждения: «Имя, паспорт и документы на квартиру мы не спрашиваем» с
отсылкой к странице ПДн (152-ФЗ) и «показываем диапазон, а не одно число»
как прямой ответ про точность (рамка диапазона осталась только в
`AccuracyV3`, рядом с плейсхолдерными числами).
Теперь в секции один порядок чтения: все шесть вопросов из `FAQ` по id
плюс три макетных возражения, которым в `FAQ` пары нет («факт сделки»,
«почему так дёшево», «подписка»). Три макетных пересказа удалены в пользу
оригиналов; у «почему так дёшево» убран второй абзац — почти дословный
повтор второго абзаца `how-accurate`, который теперь стоит следующим.
Новых формулировок не добавлено. Отсылка к странице ПДн стала живой
ссылкой через `PublicLink` (обычный `<a>`, basePath не течёт); текст
ссылки — слова самого `content.ts`, найденные по вхождению.
Гейт стал двусторонним: тест перебирает `FAQ` и требует, чтобы КАЖДЫЙ id
был отрисован. Прежняя версия перечисляла два id руками и потому молчала
ровно в тот момент, когда четыре ответа исчезли со страницы. Проверено
мутацией: снятие вопроса, переименование id и переформулировка фразы про
ПДн красят тест по отдельности.
CADDY. `/trade-in/mera-public/v3` выбыл из @meraLongPages вместе со
страницей и никуда не попал — длинный адрес превью падал в catch-all 404,
хотя раньше вёл на страницу. Обе его формы добавлены в @meraV3Gone. Там же
редирект перестал терять query: было `redir * /`, стало `redir * {uri}`
со срезанием пути — как у @meraLongPages, где перенос UTM и обоснован.
Проверено на живом Caddy 2.11 по настоящему site-блоку: все четыре формы
дают 301 на `/` с сохранённой query, без параметров — чистый `/` без
хвоста `?`, соседние матчеры не задеты, лишний сегмент по-прежнему 404.
КОММЕНТАРИИ, ОПИСЫВАЮЩИЕ НЕСУЩЕСТВУЮЩЕЕ. Строка `page.tsx` в гейте
плейсхолдеров убрана: комментарий над ней утверждал, что устаревший путь
«перестал бы что-либо сканировать», но ни в корневом `page.tsx`, ни в
прежнем `v3/page.tsx` нет ни одного имени из `PLACEHOLDER_EXPORTS` —
страница только собирает секции. Замер подтверждает: множество найденных
плейсхолдеров со строкой и без неё совпадает, а сама по себе она не даёт
ничего. Держать её значило держать ложное ощущение охвата.
Дескриптор «Оценка вторичного жилья по рыночным данным · <регион>» на
внутренние страницы не возвращается — причина записана в докстринге
`InnerHeader`: эту шапку носят и `/estimate`, `/articles`, `/docs`, где
дескриптора не было никогда; требование юриста было про первый экран
лэндинга и там выполняется `HeroV3`; на самих юр-страницах то же сказано
сильнее — в тексте оферты (п. 1.3, 6.2) и в `FooterV3` на каждой странице.
|
|||
| 6976ca8915 |
feat(mera/b2c): лэндинг v3 становится корнем публичного домена
Вариант дизайна выбран — превью с /mera-public/v3 переехало в корень целиком, вместе с блоками, перенесёнными из v1 предыдущим шагом. Каталог v3/ и компоненты v1 (Hero / HowItWorks / WhatYouGet / DataSources / Faq / AddressForm / SiteHeader / SiteFooter) удалены: их данные живут в content.ts и уже читаются секциями v3, поэтому ушла только вёрстка. Юридические страницы (оферта, возврат, ПДн) держались на SiteHeader/ SiteFooter — единственные, кто их ещё импортировал. Переведены на InnerHeader + FooterV3, тем же способом, что /estimate, /articles и /docs. ПЕРИМЕТР. /v3 убран из @meraPages, @meraShortSlash и @meraLongPages, но не выброшен: адрес две недели был открыт наружу и разошёлся по закладкам, поэтому вместо тишины белого списка — 301 на корень (@meraV3Gone, обе формы). На это заведена проверка в public-perimeter.test.ts по образцу соседних; PREVIEW_ONLY стал пустым, а не удалённым — он держит проверку «наружу не открыто ничего сверх известных страниц» двусторонней. Из PUBLIC_SHORT_PATHS гварда /v3 убран: редирект отрабатывает в Caddy, до приложения запрос не доходит, а недостижимая строка в списке публичных путей сделала бы публичной будущую страницу с тем же адресом. Гейт Caddy→гвард односторонний и сам бы этого не поймал — обоснование в коде. NOINDEX ОСТАВЛЕН. Он держится за плейсхолдерные числа marketing-v3.ts (#2904), а не за то, какой вариант дизайна стоит в корне. Путь в гейте обновлён на page.tsx: placeholdersInUse() молча пропускает ненайденный файл, поэтому устаревшая строка не покраснела бы, а перестала бы сканировать. |
|||
| d4e106882c |
feat(mera/b2c): перенести потерянные блоки v1 в лэндинг v3
Корень meraocenka.ru отдаёт v1, новый дизайн живёт на /v3 и скоро станет корнем. Шесть вещей из v1 в v3 отсутствовали — при переключении они бы просто исчезли с публичного сайта: - реестра источников не было вовсе (SourcesV3, из SOURCE_GROUPS); - «что вы получите» и дисклеймера 135-ФЗ не было (WhatYouGetV3): о том, что это не отчёт аккредитованного оценщика, узнавал только тот, кто сам догадался открыть нужное возражение; - герой не называл границу покрытия (полное — Екатеринбург, частично — остальные города области, по другим регионам не считаем) и не говорил, что мы не покупаем квартиры и не берём их на продажу; - два вопроса FAQ («откуда вы знаете, сколько стоит именно моя квартира», «почему только Свердловская область») в возражениях v3 не звучали — теперь берутся из FAQ по id, а не копией строк; - карточка проверки не спрашивала город: гео-гейт #2576 на новом лэндинге был сломан (без города «Ленина, 1» уходит в одноимённую улицу ЕКБ); - она же спрашивала состояние обязательным полем и выбрасывала ответ на переходе — теперь он доезжает в черновике. Тексты перенесены дословно (проходили юр-ревью), новых чисел нет. Новые секции встали после «Точности»: сверяем прогноз с фактом → на каких данных → что получаете на руки, всё до первого разговора про деньги. Гейт: __tests__/v3-ported-copy.test.ts — проверен мутацией (снятие секции со страницы и переименование id вопроса красят его). |
|||
| 2d8edd2c4f |
feat(mera/b2c): раздел статей и хаб документов (макет 29.08) + числа статьи заменены на замер
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-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
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
Последняя часть макета Макса «B2C модуль для МЕРА»: /articles, /articles/kak-ocenit-kvartiru, /docs. Проводка ссылок доведена до конца — «Статьи» и «Проверьте себя» в шапке и подвале перестали быть заглушками, EstimateHeader переименован в InnerHeader (он теперь стоит на трёх страницах, а не только на проверке). ЧИСЛА СТАТЬИ — НЕ ИЗ МАКЕТА. Макет утверждал «по нашим данным, средний разрыв между первой ценой в объявлении и ценой сделки — 4,1%, а у провисевших дольше трёх месяцев доходит до 9%». Прод-замер (poincare, 29.08) показал, что величины такого рода у нас нет вовсе: связки листинга со сделкой в данных не существует (cadastral_number пуст у всех 108 623 сделок и 111 693 объявлений). Заменено на то, что измеряется парно и честно: по 6202 объявлениям Домклик в ЕКБ (срез 18.07.2026) цену снижали 44%, а среди провисевших дольше трёх месяцев — 62% в медиане на 4,9%. Границы замера названы в самом тексте: это движение цены В ОБЪЯВЛЕНИИ, а не скидка на сделке, и объявлений моложе трёх недель в выборке нет (прогон обогащения их не захватил), поэтому по всему живому рынку доля ниже. Заодно сняты остальные непроверяемые утверждения: «расхождение с кадастровой до 40%» (кадастровой стоимости ПОМЕЩЕНИЙ нет ни в одной базе — проверить нечем), поправки §3 помечены как рыночная практика, а не наш замер, пороги фильтра §2 приведены к тем, что реально стоят в оценщике (радиус 1 км, площадь ±15%, минимум 5 аналогов + каскад послаблений). Плейсхолдеров marketing-v3 в статье не осталось. Периметр закрыт во всех трёх местах: @meraPages / @meraShortSlash / @meraLongPages в apps.caddy, PUBLIC_ROUTES и PUBLIC_SHORT_PATHS в гварде. @meraShortSlash до сих пор не проверялся ничем — добавлен гейт в public-perimeter.test.ts (фальсифицирован: снятие /docs из альтернации красит тест). Гейт #2904 теперь видит и статью (articles-content.ts в V3_SOURCES) — фальсифицировано отдельно: с одним этим файлом в списке снятие noindex красит гейт. Честные отличия от макета — в докстрингах страниц: масок реквизитов и макетной редакции оферты нет (источник правды — /oferta, /privacy, /refund от 13.08), регламент ответа поддержки «9:00-19:00» снят (никто его не устанавливал), фильтр рубрик и обещание «новые статьи каждую неделю» не рендерятся. Проверено: tsc, eslint, vitest 69/69, изоляция mera-public, скриншоты трёх страниц на 1180/375/320px без горизонтального переполнения. |
|||
| 51fd939f5c |
Merge pull request 'feat(mera/b2c): результат проверки в облике отчёта (макет 29.08 «МЕРА отчёт»)' (#3221) from feat/b2c-report-redesign into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
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 2m6s
Deploy Trade-In / deploy (push) Successful in 55s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
| de66826b25 |
feat(mera/b2c): результат проверки в облике отчёта (макет 29.08 «МЕРА отчёт»)
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 59s
Редизайн результата /estimate по макету отчёта: шапка объекта (адрес + чипы параметров + «Изменить данные»), две большие бесплатные карточки, тизер платной части с shimmer-строками, честные состояния «мало данных» и «город не покрыт» в стилистике «ЧЕСТНЫЙ ОТВЕТ». Сознательные расхождения с макетом (задокументированы в шапке компонента): - «✓ расчёт готов и ждёт вас» не воспроизводится — платного расчёта для анонима не существует (анонимный /estimate закрыт, платёжного контура нет, #2896); тизер честно называется «что будет в полном отчёте», вместо кнопки оплаты — прежняя формулировка «приём оплаты подключается»; - бейджи МИР/СБП/VISA — за тем же TODO платёжного контура, что и в FooterV3; - email-подписка «сообщим когда добавим» — нет хранения и согласия (#2895), вместо неё живой канал поддержки; - «Расширить радиус до 3 км» — /coverage радиус не параметризует; - подписи бесплатных плиток — запиненные честные из coverage-copy. Возврат 150 ₽ — строкой из макета со ссылкой на /refund. Старый CoverageResult удалён (один рендер результата, не два). Фикстурное превью всех трёх тонов — /mera-public/ui-preview/report. Проверено: tsc чисто, lint чисто, vitest 27 passed, скрин трёх состояний. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 9f45c7616c |
Merge pull request 'chore(ci): node 20 отслужил — рантайм фронтов и оба CI-джоба на node 24 LTS' (#3214) from chore/node24-runtime into main
Some checks failed
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 12s
Deploy / deploy-caddy (push) Has been skipped
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 3m22s
Deploy Trade-In / deploy (push) Successful in 58s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy / build-frontend (push) Successful in 4m38s
Deploy Trade-In / perimeter-smoke (push) Successful in 14s
Deploy / deploy (push) Failing after 15m16s
Deploy / perimeter-smoke (push) Has been skipped
Deploy / deploy-status (push) Failing after 1s
|
|||
| a3e642fed4 |
Merge pull request 'chore(deps/mera): vitest 2 → 3 — заодно уходит вся dev-цепочка vite/esbuild' (#3216) from chore/vitest3-mera into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 3m24s
Deploy Trade-In / deploy (push) Successful in 59s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 14s
|
|||
| 2f91b4b34f |
Merge pull request 'feat(mera/b2c): лэндинг v3 доведён до макета 29.08 — лента, игра, два пути, возражения, статьи' (#3215) from feat/b2c-landing-v3-full into main
Some checks are pending
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (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 13s
Deploy Trade-In / test (push) Has been skipped
|
|||
|
|
57ec24f364 |
chore(deps/mera): vitest 2 → 3 — заодно уходит вся dev-цепочка vite/esbuild
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / openapi-codegen-check (pull_request) Has been skipped
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 / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m18s
Мажор ради GHSA-5xrq-8626-4rwp (CRITICAL, vitest ≤2.x). Сам CRITICAL к нам неприменим — он про `--ui`, а `@vitest/ui` не установлен и скриптов с `--ui` нет — но держать заведомо непатченный рантайм тестов ради этого рассуждения не стоит: следующий, кто добавит `--ui`, про оговорку не узнает. Побочно закрылось больше, чем планировалось. vitest тянет vite сам, и диапазон у мажора другой: 2.1.9 → vite ^5.0.0 (резолв 5.4.21 → esbuild 0.21.5), 3.2.7 → vite ^5||^6||^7 (резолв 7.3.6 → esbuild 0.28.2). Так что одним мажором: vitest 2.1.9 → 3.2.7 GHSA-5xrq-8626-4rwp vite 5.4.21 → 7.3.6 GHSA-4w7w-66w2-5vf9, GHSA-fx2h-pf6j-xcff, GHSA-v6wh-96g9-6wx3 esbuild 0.21.5 → 0.28.2 GHSA-67mh-4wv8-2f99 OSV по локу: 4 → 1. Остаток — postcss 8.4.31, вендоренный ВНУТРИ next/node_modules; нашим локом не управляется вообще. @vitejs/plugin-react 4.3.4 → 5.2.0 — вынужденно и ровно поэтому: у 4.x peer на vite ^4||^5||^6, семёрка в него не попадает. У 5.2.0 — ^4||^5||^6||^7. В шестёрку не идём: там peer уже ^8.0.0. vitest.config.ts править не пришлось: в нём нет ничего из того, что мажор переименовал или убрал (ни environmentMatchGlobs, ни deps.inline, ни workspace) — только environment/globals/setupFiles/include, которые в 3.x как были. Приёмка (локально, node 26): npm run test 66 passed, 2 failed npm run type-check ok Те же самые 2 падения (LoginPage, ветки 429/401) дают и vitest 2 на этом же локе, и vitest 2 на локе ДО апдейта зависимостей — проверено прогоном в обеих конфигурациях. Это локальный node 26 против node 20/24 в CI, к мажору отношения не имеет. Гейт — CI. |
||
| be7d7d0205 |
feat(mera/b2c): лэндинг v3 доведён до макета 29.08 — лента, игра, два пути, возражения, статьи
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 Trade-In / frontend-checks (pull_request) Successful in 1m18s
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
Макет «B2C модуль для МЕРА» (Макс, 29.08) — пять секций, которых превью /mera-public/v3 не имело (его шапка их и перечисляла как отсутствующие): - DealsTickerV3 — бегущая строка «ПРОГНОЗ → ФАКТ»; данные — ТЕ ЖЕ строки, что таблица сверок (PROOF_ROWS_PLACEHOLDER): один будущий контур — один набор реквизита, второй не заводится; - GuessGameV3 — игра «Угадай цену», три раунда со слайдером, сверка «ваш ответ / МЕРА / факт», итог с медианной ошибкой игрока; раунды — GAME_ROUNDS_PLACEHOLDER под гейтом #2904; - TwoPathsV3 — «сами / сделаем за вас»; заявка пути 2 ведёт в Telegram поддержки (формы и договора «под ключ» не существует); - ObjectionsV3 — FAQ «перед оплатой» на нативных details; ответы из выверенной честной копии (content.ts/oferta/refund), макетный ответ про «отметки продавцов» заменён тем, что в продукте есть (Росреестр + снятие с публикации); - ArticlesV3 — тизер «Разборы на данных»; карточки БЕЗ ссылок до появления раздела статей (следующий шард). Решение «модалка → /estimate» (StickyCtaV3) сохранено для всех CTA макета. Гейт noindex↔плейсхолдеры дополнен новыми файлами и GAME_ROUNDS_PLACEHOLDER. Проверено: tsc чисто, next lint чисто, vitest 27 passed, живой прогон игры на dev (раунд → сверка → следующая), полностраничный скрин 1:1 с макетом. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
4e8cf5ab62 |
chore(ci): node 20 отслужил — рантайм фронтов и оба CI-джоба на node 24 LTS
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 13s
CI Trade-In / browser-tests (pull_request) Successful in 1m16s
CI Trade-In / frontend-checks (pull_request) Successful in 1m30s
CI / frontend-tests (pull_request) Successful in 1m23s
CI / openapi-codegen-check (pull_request) Successful in 2m23s
CI Trade-In / backend-tests (pull_request) Successful in 5m47s
CI / backend-tests (pull_request) Successful in 18m12s
Node 20 вышел из поддержки 30.04.2026: security-патчи для него больше не выпускаются, а образ node:20-alpine продолжает собираться и молча уносить это в прод. Node 24 — текущая Active LTS. Меняется ровно major рантайма, больше ничего: frontend/Dockerfile node:20-alpine → node:24-alpine (deps/builder/runner) tradein-mvp/frontend/Dockerfile то же, три стадии .forgejo/workflows/ci.yml node-version "20" → "24" (два джоба) .forgejo/workflows/ci-tradein.yml то же (один джоб) Версия в CI намеренно держится равной major'у из Dockerfile — так было и раньше, комментарии рядом обновлены вместе с числом, чтобы не разошлись. Ни `engines`, ни `.nvmrc` в проекте нет — других мест, где закреплён major, не осталось (проверено grep'ом по Dockerfile/yml/md). Совместимость: next 15.5.24 поддерживает node 20/22/24; sharp 0.35.4 — node ^18.17 || ^20.3 || >=22, prebuild linuxmusl-x64 есть. Приёмка — этот самый CI: джобы фронтов теперь выполняются на node 24, так что зелёный прогон PR и есть доказательство. Локально проверить нечем — на машине node 26, это не тот major. |
||
|
|
b52ec68aad |
fix(deps/mera): 6 уязвимых пакетов во фронт-локе — пересборка без правки package.json
All checks were successful
CI Trade-In / changes (pull_request) Successful in 14s
CI / changes (pull_request) Successful in 15s
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) 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
`npm update --package-lock-only` в tradein-mvp/frontend: package.json не тронут,
все диапазоны прежние — обновился только резолв внутри них.
OSV по локу: 6 уязвимых версий → 4. Закрыто:
sharp 0.34.5 → 0.35.4 GHSA-f88m-g3jw-g9cj
nanoid 3.3.17 → 3.3.18 GHSA-2v37-7h3g-55p8
Заодно (без CVE, в тех же диапазонах): next/@next/* 15.5.23 → 15.5.24,
js-yaml 4.3.1 → 4.3.2, @tanstack/react-query 5.101.4 → 5.102.8,
@typescript-eslint/* 8.66.0 → 8.68.0, rollup 4.62.4 → 4.63.1,
@testing-library/react 16.3.2 → 16.3.3, ws 8.21.2 → 8.21.3.
Остаток (4) диапазонами не чинится, вынесен в отдельную задачу:
postcss 8.4.31 — вендорится ВНУТРИ next/node_modules, нашим локом не управляется;
vitest 2.1.9 → vite 5.4.21 → esbuild 0.21.5 — dev-цепочка, лечится мажором
vitest 2 → 3.2.6+. GHSA-5xrq-8626-4rwp (CRITICAL) при этом неприменим:
@vitest/ui не установлен, скриптов с --ui нет.
Приёмка (локально, node 26):
npm ci --legacy-peer-deps — 566 пакетов, ok
npm run build — ok, весь роут-набор собрался
npm run test — 66 passed, 2 failed (LoginPage 429/401)
Два падения НЕ от апдейта: те же 2 теста падают ровно так же на СТАРОМ локе
(проверено откатом лока + npm ci + прогоном того же файла). Локальный node 26
против node 20 в CI; гейт — CI.
Хойстинг в диффе выглядит как даунгрейд, но им не является: picomatch 2.3.2
переехал из micromatch/node_modules/ наверх, а 4.0.5 → 4.0.7 ушёл под
tinyglobby/ вместе с fdir 6.5.0. Реального понижения версий нет.
NB: npm 12 локально требует --allow-remote=all даже при --package-lock-only
(ничего не ставится, только резолв). В CI npm 10 — там ограничения нет.
|
||
|
|
1dcddec9e1 |
fix(mera/v2): плашка версии лежала на главной кнопке и съедала клик
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 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 57s
Замер на проде 27.08 (окно 1512×900): плашка стояла в левом нижнем углу (`left:16; bottom:16`), а туда же приходит липкая кнопка «ОЦЕНИТЬ КВАРТИРУ» из панели параметров. Прямоугольники: кнопка 59…484 по X и 859…904 по Y, плашка 16…284 и 859…884 — перекрытие по всей высоте плашки. Следствий два, и второе хуже первого. Надпись на кнопке читалась разорванной. И `document.elementFromPoint` в центре кнопки возвращал плашку: клик по левой трети главного действия продукта не доходил до кнопки вообще. Плашка переезжает в правый нижний угол, НАД кнопку поддержки (44px от низа, z-index 25). Там свободно: ниже только пассивная «Сводка объекта» без органов управления — перекрыть её краем ничего не стоит. Вдобавок плашка становится сквозной для указателя, а ссылка «История версий» внутри — нет. Это страхует от повторения: что бы под плашкой ни оказалось в будущем, клик достанется ему, а не ей. Три теста закрепляют оба следствия разбора: якорь не левый, указатель проходит насквозь, ссылка жива. Прогон: 68 тестов зелёные, tsc чист. |
||
|
|
e85cafd9a9 |
fix(mera/b2c): подсказка дублировала адрес, а числа в результате спорили
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 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 1m0s
Оба дефекта найдены проходом клиентского пути на проде 27.08.
ПОДСКАЗКА АДРЕСА печатала одно и то же дважды. Строка списка состоит из
главной строки (`label`) и пояснительной (`full_address`), но подсказчик
для дома отдаёт их совпадающими буква в букву — проверено на проде:
{"label":"Свердловская область, г. Екатеринбург, ул. Малышева, д. 51",
"full_address":"Свердловская область, г. Екатеринбург, ул. Малышева, д. 51"}
Человек видел адрес продублированным в каждой строке выпадающего списка.
Показываем вторую строку только когда она отличается: замысел (короткое имя
сверху, полный адрес под ним) сохраняется, если подсказчик когда-нибудь
начнёт их различать.
ЧИСЛА В РЕЗУЛЬТАТЕ спорили друг с другом. Плитка сверху: «30 похожих
квартир продаётся в радиусе 1 км». Плитка под ней: «столько в среднем висит
объявление из этих 6». Слово «этих» указывает на 30, а стоит рядом 6 —
читается как ошибка в расчёте.
Шесть — это те объявления, у которых известна дата публикации; медиана
считается только по ним. Теперь доля названа явно («дата известна у 6 из
30»), а когда известна у всех — не упоминается вовсе, чтобы «30 из 30» не
шумело. Честное имя величины сохранено: это возраст активного объявления,
а не срок продажи (тест на это как был, так и остался).
Проверено: 27 тестов mera-public зелёные, tsc --noEmit чист.
|
||
|
|
0231338a7f |
fix(mera/ui): карта перестала перекрывать интерфейс, страница помещается в окно
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 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 1m6s
Три правки, две причины — обе найдены прогоном по живому проду 27.08 под аккаунтом админа. ## Карта поверх всего (два симптома, один корень) Контейнер мини-карты в HeroBar имел position: relative БЕЗ z-index. Такой блок контекст наложения не создаёт, поэтому внутренние слои Leaflet (тайлы 200, оверлеи 400, маркер 600, атрибуция 800) и карточка адреса (750) конкурировали не между собой, а со всей страницей. Отсюда: • меню аккаунта (UserMenu, z-index 200) открывалось ПОД картой; • карточка «АДРЕС» всплывала поверх таблицы раздела «Продажи в доме». Лечится изоляцией контейнера (isolation: isolate), а не гонкой чисел: перебивать 800 у атрибуции пришлось бы в каждом новом элементе, и гонка возвращалась бы. Меню шапки заодно поднято до 1000 — оно обязано быть сверху по замыслу, а не по совпадению. ## Страница не помещалась в окно Масштаб артборда 1536×1024 считался от window.innerWidth, который ВКЛЮЧАЕТ вертикальную полосу прокрутки. На рабочей области 1425px артборд выходил 1440px — страница получала постоянный горизонтальный скролл. Воспроизводится на любом десктопе: страница длинная, вертикальная полоса есть всегда. Считаем от document.documentElement.clientWidth. Пороги isMobile/ isSmallViewport намеренно оставлены на innerWidth: это классификация устройства, а не расчёт геометрии. |
||
| a4604dd9db |
fix(mera/ui): карточка прогресса перестала выдумывать стадии (#3081, вариант A)
All checks were successful
CI / changes (pull_request) Successful in 11s
CI / frontend-tests (pull_request) Has been skipped
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 Trade-In / frontend-checks (pull_request) Successful in 59s
Запрос оценки один и блокирующий (POST /estimate, mutation.isPending) — промежуточных событий по источникам не существует, а карточка рисовала пофайловые «сбор...» с полосками 60%, счётчик 0/5 и подпись про Celery group с таймаутом. Пользователь не мог отличить «думает» от «завис», а серверная механика текла в UI-текст. Вариант A из задачи (только фронт): - на pending строки нейтральны («ожидает ответ»), раскраска — только по факту estimate.sources_used после ответа; - общая полоса на pending — честная неопределённая анимация вместо выдуманного процента (Math.min(95, ...) убран); - счётчик N/M на pending заменён на «опрашиваем…» (0/5 при идущей работе читался как отказ); - подпись без Celery/таймаута, тон legacy-заглушки «Считаем оценку…»; - недостижимые ветки error/loading у строк удалены (их ничто не выставляло). Вариант B (реальный статус с бэкенда) отклонён сознательно: /estimate остаётся синхронным по решениям #3082/#3083, городить async+task_id ради прогресс-бара — против них. Preview-страница /ui-preview/estimate теперь рендерит ОБА состояния карточки. Проверено скриншотом на dev (pending + done). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 60cb510ef2 |
refactor(caddy): разделить site-блоки по хостам под переезд (#3062)
Some checks failed
Deploy / changes (push) Successful in 8s
Deploy Trade-In / changes (push) Successful in 12s
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-worker (push) Successful in 48s
Deploy / build-backend (push) Successful in 49s
Deploy / build-frontend (push) Successful in 49s
Deploy / deploy (push) Successful in 1m37s
Deploy / deploy-status (push) Successful in 1s
Deploy Trade-In / build-frontend (push) Successful in 2m34s
Deploy / perimeter-smoke (push) Successful in 10s
Deploy Trade-In / test (push) Failing after 3m56s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Has been skipped
Deploy Trade-In / perimeter-smoke (push) Has been skipped
Deploy Trade-In / deploy-status (push) Failing after 1s
|
|||
| 4ae14055ec |
feat(tradein/v2): позиция квартиры на рынке — плашка на карточке рекомендованной цены (#2899) (#3024)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
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 2m33s
Deploy Trade-In / deploy (push) Successful in 1m46s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 18s
|
|||
| 8db357ef30 |
test(tradein): гейт «снять noindex можно только вместе с плейсхолдерами» (#2904) (#2948)
All checks were successful
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 / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 3m18s
Deploy Trade-In / deploy (push) Successful in 1m6s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
|
|
73c4c487ed |
fix(mera/b2c): житель Серова получал «вы вне области», а подсказки игнорировали выбранный город
All checks were successful
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 1m6s
CI Trade-In / backend-tests (pull_request) Successful in 4m50s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 48s
Два дефекта, найденных прогоном сценария глазами посетителя на живом домене. ## 1. Город предлагали выбрать, но отвечать по нему не умели Дропдаун на сайте (`OBLAST_CITIES`, city-registry.ts) и списки покрытия (`COVERAGE_GREEN/YELLOW_CITIES`, trade_in.py) — одно множество, записанное в двух местах. Они разошлись в обе стороны: предлагали, но не отвечали: Серов отвечали, но не предлагали: Берёзовский, Среднеуральск, Ревда Житель Серова выбирал СВОЙ город из НАШЕГО дропдауна и получал: «Этот адрес вне области, по которой мы собираем данные. Сейчас это Свердловская область: Екатеринбург целиком и ещё несколько городов вокруг.» Про город в той же самой области. Серов при этом покрыт данными: 363 активных объявления в радиусе 15 км, все свежие (замер по проде). Поэтому добавлен в жёлтый тир, а не убран из дропдаунa; три недостающих города добавлены на фронт. Шапка city-registry.ts этот риск прямо предсказывала — «перед добавлением 7-го города сверить оба списка вручную, теста на это пока нет». Теперь тест есть: бэкендовый сьют читает TS-реестр и требует РАВЕНСТВА множеств. Плюс проверка, что у каждого города с порогом есть центроид, — иначе порог мёртвый, город по координатам не резолвится. ## 2. Подсказки не слушались выбранного города `city_hint` доезжает до геокодера, но на выдачу не влияет: его смотрит только екатеринбургский кадастровый тир (как признак «речь не про ЕКБ, тир пропускаем»), а DaData-тир ограничен регионом целиком и хинта не принимает. Замер: выбран Серов, введено «Ленина 1» → первой подсказкой «Невьянский р-н, пгт Верх-Нейвинский». Человек выбирает верхний вариант и считает чужой дом — ровно баг #2576, ради которого город и спрашивают. Публичная ручка теперь подставляет город в саму строку запроса. Проверено на проде: «Серов Ленина 1» даёт серовскую выдачу целиком. Для Екатеринбурга подстановка безвредна — три разных адреса дали тот же результат с префиксом и без, поэтому правило одно на все города, без исключения для основного трафика. Чинится в публичной ручке, а не в геокодере: там от `city_hint` зависит поведение закрытого контура (`target_city_ambiguous`). ## Фикстура теста `_FAR_AWAY_CITY` стояла в 21 км от центра Серова и работала как «далеко от всех» лишь потому, что Серов не был поддержан. Переехала в Тавду — 271 км до ближайшего центроида. ## Мутации убрать Серов из покрытия (состояние прода) → падает сверка списков не подставлять город в строку → падает проверка ручки откат → 21 passed Плюс backend 75 passed, vitest 56 passed, tsc, lint, build, isolation guard. `city-registry.ts` добавлен в paths-фильтр БЭКЕНДОВОГО лэйна: сверку списков делает бэкендовый тест, и без этой строки правка одного лишь дропдауна её бы не запускала — то есть ровно тот путь, которым списки и разошлись. |
||
|
|
e74e9106b3 |
fix(mera/b2c): /estimate снова закрывался гвардом — короткий адрес завели в Caddy, но не в RouteGuard
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 / 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 55s
Замер на проде сразу после выкатки #2913: страница `/estimate` отдаёт 200 и выглядит правильно, но после гидратации монтируется `GuardedRoute` — 404 /trade-in/api/v1/me ×3 404 /trade-in/api/v1/trade-in/support/anon/unread плюс виджет чата поддержки из закрытого контура и экран «Не удалось проверить доступ» внизу страницы. Форма при этом переставала работать ВОВСЕ: разметка с сервера видна, поле принимает текст, а обработчики не подключены — человек печатает адрес и не получает ни подсказок, ни ошибки. Проверено, что дело не в API: ручной `fetch` из той же страницы отдаёт 200 и восемь подсказок. Причина ровно та же, что чинил PR #2910 для `/`: список публичных путей в гварде и матчер `@meraPages` в Caddyfile — одно множество, записанное в двух местах. Тогда разошлись на `/`, теперь на `/estimate`. Оба раза страница отдавала 200, ломалась только в браузере, и ни один тест этого не видел. Комментарий в шапке гварда инцидент не предотвратил (он там был), поэтому совпадение теперь запинено ТЕСТОМ: он читает Caddyfile и требует, чтобы каждый путь из `@meraPages` проходил `isPublicPath`. Сверяемся с источником, а не дублируем список третий раз. Мутация — вернуть список к состоянию, которое уехало на прод: 2 failed / 3 passed × пропускает короткие адреса публичного домена × совпадает с тем, что публичный домен реально раздаёт (Caddyfile) откат → 5 passed. Плюс vitest 56 passed, tsc, lint, isolation guard 41 файл. `Caddyfile` уже в paths-фильтре фронтового лэйна (#2913), так что правка одного лишь конфига теперь тоже гоняет этот гейт. |
||
|
|
56194c606a |
fix(mera/b2c): семь дефектов публичного периметра, найденных состязательным ревью
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Failing after 8s
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) Successful in 46s
CI Trade-In / frontend-checks (pull_request) Successful in 1m3s
CI Trade-In / backend-tests (pull_request) Successful in 4m42s
Ревью четырьмя независимыми линзами (периметр, семантика Caddy, политика ПДн
против кода, фронт) + по два проверяющих на каждую находку. Ниже — то, что
пережило проверку и воспроизведено на живом коде, а не выведено из чтения.
## Caddy: открытый редирект и потерянные ссылки
Захват хвоста регекспом (`^/trade-in/mera-public/(.+)$` → `redir /{re…1}`) —
открытый редирект. Захват берётся из РАСКОДИРОВАННОГО пути, поэтому
`/trade-in/mera-public/%5Cevil.example/pay` даёт цель `/\evil.example/pay`, а
браузеры трактуют `/\` как `//` — Location уводит на чужой хост. Готовая
фишинговая заготовка с домена, который напечатан внутри оферты и уходит
модератору эквайера. Заменено поимённым списком путей: такой адрес просто не
матчится.
Адреса со слэшем на конце (`/oferta/`, и длинные `…/oferta/`) отдавали 404 —
ровно те ссылки, ради сохранности которых редирект и делался. Добавлена
нормализация, цепочка замкнута (проверено: 2 перехода → 200).
Query-строка терялась: размещённые ссылки с UTM приходили бы в аналитику как
прямой заход. `uri strip_prefix` + `{uri}` переносит её. Обёртка `route`
обязательна — без неё `redir` выполняется раньше `uri` и Location равен
исходному адресу (бесконечный цикл, поймано на стенде).
`/v3` — черновое превью с маркетинговыми плейсхолдерами — было открыто на
боевом домене молча. Теперь названо вслух и запинено тестом.
## Гейты, которых не было
`caddy validate` не звал НИ ОДИН workflow, а deploy применяет конфиг не через
`reload` (тот отказался бы принять битый), а через `up -d --force-recreate` —
опечатка уводит контейнер в crash-loop и роняет ВСЕ домены. Добавлен гейт в
ci.yml, тем же образом caddy:2, что и на проде.
Проверка «роут ↔ Caddy» была односторонней и пропускала обратную ошибку —
путь, открытый наружу, о котором приложение не знает. Так и уехал `/v3`.
Теперь двусторонняя, плюс проверка, что для каждой страницы есть 301.
## Бюджет внешнего геокодера
Per-IP окна ограничивают одного клиента, но не сумму: 40/мин с адреса — это
57 600 в сутки при бесплатном тире DaData в 10 000, ОБЩЕМ с закрытым контуром.
Подтверждено на проде: достаточно упомянуть не-екатеринбургский город, чтобы
локальный тир отключился и запрос гарантированно ушёл во внешний сервис. То
есть один скрипт оставлял без подсказок платящих пилотов.
Per-IP снижен до 20/мин, добавлен общий суточный потолок 2000 и потолок
одновременных подсказок (4): кадастровый тир уходит в FDW-скан чужой базы,
держит соединение около секунды, а пул общий с B2B — полтора десятка
параллельных публичных запросов клали бы закрытый контур.
## «Адрес нигде не сохраняется» — теперь правда целиком
Две утечки, обе воспроизведены:
1. ЖУРНАЛЫ. Геокодер печатает введённую строку открытым текстом на каждый
вызов, прод пишет stdout в persistent journald — адрес ложился на диск
рядом с IP того же запроса в access-логе Caddy. Закрыто фильтром логов на
время публичного запроса (contextvar, переживает await и to_thread).
Закрытый контур логи сохраняет: они нужны для разбора жалоб пилотов.
2. МОНИТОРИНГ. sentry_sdk кладёт в событие ПОЛНОЕ тело запроса — а тело
публичной ручки это ровно `{"q": "<адрес>"}`; `send_default_pii=False` тут
не гейт, он про куки. Плюс брэдкрамб httpx несёт адрес в query геокодера.
Закрыто `scrub_public_address`.
Текст п. 5.4 политики расширен до «ни в журналы веб-сервера, ни в технические
журналы, ни в мониторинг» — ровно то, что теперь обеспечено кодом.
## Фронт
- Отмена запроса подсказок откладывалась внутрь следующего debounce-такта и
не наступала вовсе, если человек переставал печатать: ответ по старой строке
долетал и ложился в список. Контроллер создаётся сразу, отменяется в cleanup.
- Список схлопывался на каждое нажатие — клик по намеченному пункту
промахивался. Старая выдача висит, пока не пришла новая.
- «Комнат» с лэндинга — свободный текст: «студия» не совпадала ни с одним
option, селект показывал пустоту, parseInt давал NaN, на сервер уходил
rooms: null → 422 с текстом «сломалось на нашей стороне». Нормализация
вынесена чистой функцией и покрыта тестами.
- У пробы покрытия не было ни таймаута, ни отмены: оборванное соединение
оставляло кнопку в «Смотрим данные…» навсегда. 15 с + понятный текст.
- Ошибка подсказок глушилась в пустой список — тупик без объяснения.
- Комбобокс: Tab проваливался в кнопки подсказок, список не закрывался по
уходу фокуса и перекрывал поля, Escape оставлял висячий aria-activedescendant.
## Проверено
Локальный стенд (реальный site-блок Caddy + заглушка): 18 маршрутов, включая
`%5C`, `//`, `%2F` — все три теперь 404. vitest 55 passed, backend 17 passed по
публичному API, tsc, lint, build, isolation guard 41 файл, caddy validate.
Мутации: снять редакцию логов → падает тест журналов; не вырезать тело запроса
→ падает тест мониторинга; убрать /estimate из Caddy → падает тест маршрутов.
|
||
|
|
7424c283d5 |
feat(mera/b2c): отдельный экран оценки на meraocenka.ru и короткие адреса
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 8s
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) Successful in 44s
CI Trade-In / frontend-checks (pull_request) Successful in 1m2s
CI Trade-In / backend-tests (pull_request) Successful in 4m41s
## Экран проверки — /estimate Проверка квартиры вынесена на собственный адрес: там у автокомплита есть место под список подсказок, а у результата — место рядом с полями. Форма в герое лэндинга осталась входной точкой и уводит сюда, донося набранное через sessionStorage (НЕ через query — адрес в URL попал бы в access-лог Caddy рядом с IP посетителя, а мы на той же странице обещаем ничего не хранить). Показывает живую пробу покрытия: сколько похожих квартир продаётся рядом и сколько в среднем висят их объявления. Ни одной рублёвой цифры — цену продаёт платный шаг. Тексты вердикта вынесены чистой функцией (coverage-copy.ts) и покрыты тестами: подпись под возрастом обязана говорить «объявление», а не «продаётся» (выборка цензурирована), при неизвестном возрасте плитки нет вообще, а пустая когорта объясняется как факт о рынке с подсказкой, что поменять, — директива «никогда не блокировать вывод». ## Короткие адреса Человек больше не видит /trade-in/mera-public/... — только /, /estimate, /oferta, /refund, /privacy. Длинные адреса отдают 301 на короткие: у страницы один канонический адрес, старые ссылки живы. Цена решения: ссылки работают только на meraocenka.ru (короткие пути раздаёт этот хост). Открывать лэндинг для проверки нужно там же, а не с gendsgn.ru. Ссылки эмитятся обычным <a> (PublicLink) — next/link подставляет basePath, и href="/estimate" уехал бы на несуществующий /trade-in/estimate. ## Три дефекта, найденных на живом сайте 1. Палитра v3 никуда не доезжала. b2c-tokens.ts не импортировал НИКТО, ни одна --b2c-* переменная не объявлялась, каскад молча пропускал такие декларации — лэндинг отдавал 200 бесцветным. Добавлен мост b2cVars, гейтом стал тест: каждая использованная в CSS переменная обязана быть объявлена. 2. Голый /trade-in/mera-public падал в 404 — матчер был со слэшем и звёздочкой. Ровно туда вела «Главная» в подвале. 3. «Для бизнеса» вела на «/» — то есть на сам лэндинг. Теперь абсолютный адрес B2B-контура. Пункты «Проверьте себя» и «Продажа под ключ» вели на якоря, которых нет нигде: приведены к виду «Статьи» — видны, но не кликабельны. ## Периметр и приватность Подсказки переведены на POST: access-лог публичного домена пишет URI целиком, то есть GET с ?q= сохранял бы адрес квартиры в файл. Тело в лог не попадает. Метод запинен тестом — это часть обещания, а не стиль. П. 5.4 политики ПДн переписан ВМЕСТЕ с кодом: прежний текст утверждал, что адрес не покидает браузер, и это перестало быть правдой. Новый говорит точно — передаётся, используется однократно, в базах не сохраняется. Последнее проверено по коду: suggest() работает без кэша, проба — один SELECT, аудит пишет строку только при наличии username. PUBLIC_ESTIMATE_ENABLED сузился до платного шага (бесплатная проба не хранит ничего, платный расчёт хранит). ## Проверено vitest 47 passed (9 файлов), tsc, next lint, next build, isolation guard 40 файлов, backend 75 passed, caddy validate = Valid configuration. Мутации структурных гейтов: убрать --b2c-accent-text из b2cVars → падает тест палитры убрать /estimate из @meraPages → падает тест маршрутов добавить импорт next/link → падает тест basePath откат → 14 passed Caddyfile добавлен в paths-filter фронтового лэйна: его читает тест маршрутов, и без этой строки правка одного лишь Caddyfile не запускала бы ни один гейт. Refs #2894, #2895 |
||
|
|
6348f51ff4 |
fix(mera/b2c): публичный лэндинг больше не показывает «Не удалось проверить доступ»
All checks were successful
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 54s
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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
На meraocenka.ru посетитель видел внизу страницы экран проверки доступа из
закрытого контура. Замер на проде: три вызова GET /trade-in/api/v1/me → 404
плюс подтянутый следом клиент чата поддержки.
Причина. Caddy на коротком домене переписывает путь ВНУТРЕННЕ (rewrite, не
redir): посетитель остаётся на meraocenka.ru/, а Next получает
/trade-in/mera-public. Поэтому SSR видит /mera-public и bypass срабатывает, а
после гидратации usePathname() возвращает «/» — путь из адресной строки — и
bypass отваливается. Монтируется GuardedRoute, летит запрос к /me, а на этом
хосте Caddy намеренно не проксирует /trade-in/api/* → 404 → NoAccessScreen.
В шапке RouteGuard прямо стояло допущение «домен/rewrite настраиваются
отдельным PR — на поведение гварда это не влияет, он смотрит на путь». Оно
сломалось ровно в тот момент, когда домен появился; заменено разбором факта.
Правка: четыре коротких адреса публичного домена (/, /oferta, /refund,
/privacy) добавлены в bypass ТОЧНЫМ совпадением, отдельной веткой. Префиксную
проверку для «/» применять нельзя — startsWith("/") истинно для любого пути,
то есть один такой элемент сделал бы публичным всё приложение. Эти адреса
существуют только на публичном домене: на gendsgn.ru их ловит catch-all 404, а
«/» отдаёт server-side redirect на /v2 из next.config, то есть приложение по
этому пути не рендерится вовсе.
Тест на оба инварианта: короткие адреса публичны И закрытый контур (/v2,
/admin/*, /history, /scrapers/*, /login) — нет. Мутация «точное совпадение →
префиксное» роняет два теста из четырёх, проверено прогоном.
Проверено: vitest 33 passed (7 файлов), tsc --noEmit, isolation guard 31 файл.
|
||
|
|
8f954fb98c |
feat(mera/b2c): лэндинг v3 по макету — превью на /mera-public/v3
All checks were successful
CI Trade-In / changes (pull_request) Successful in 16s
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 1m13s
Дизайн-макет владельца (15.08.2026). Собрано рядом с живым лэндингом, а не вместо него: /mera-public открыт на meraocenka.ru, менять его посреди согласования дизайна нельзя. - b2c-tokens.ts — палитра макета отдельно от v2/tokens.ts. Красить теми же токенами B2B-экран нельзя, это другой продукт; hex-литералы остаются в одном файле. - marketing-v3.ts — числа из макета в ОДНОМ месте, с таблицей расхождений с нашими замерами и гейтом: заменить вычисляемыми до снятия noindex. - _components/v3/ — шапка с селектором города, подвал, липкая CTA, герой с карточкой проверки, три шага, «Точность», «Цена ошибки». - layout.tsx больше не рендерит шапку/подвал: вложенный layout в Next ДОБАВЛЯЕТСЯ к родительскому, поэтому v3 со своим хромом получал бы две шапки. Теперь хром выбирает страница — v1 берёт SiteHeader/SiteFooter, v3 свой. Отступления от макета (все задокументированы в коде): - реквизиты и документы берутся из LEGAL_ENTITY, а не из маски «ООО «МЕРА» · ИНН 66700XXXXX» в макете; - в подвал добавлены Политика возврата и почта поддержки, которых в макете нет, — обязательные элементы платного контура; - бейджи МИР/СБП/VISA не рендерятся: приём платежей не подключён; - форма не выдаёт результат — анонимного расчёта нет, а одинаковые «14 объявлений» на любой адрес это обман конкретного человека, а не продающая формулировка. Ветка результата свёрстана и ждёт /coverage; - замыленная рублёвая цифра не перенесена: в макете она лежит в разметке текстом под filter:blur и достаётся из исходника; - акцент #16A9BC не используется как цвет текста (2,6:1 на фоне страницы) — для текста затемнённый #0B6B79, 5,8:1; - города и объёмы в селекторе — из нашего реестра, а не 42 города РФ. Секции макета, требующие отсутствующих данных и контуров (лента прогноз→факт, игра, продажа под ключ, статьи, модалка, оплата), не верстались — заведены задачами #2894-#2904. Проверено: tsc --noEmit, next lint, isolation guard (31 файл), next build — /mera-public/v3 пререндерится статикой. |
||
|
|
f1491068f4 |
merge main: объединить legal-copy.ts (PRIVACY_PATH + SHORT_ESTIMATE_DISCLAIMER)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
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 1m29s
CI Trade-In / backend-tests (pull_request) Successful in 4m49s
Оба модуля были созданы параллельно в разных ветках под один и тот же файл: здесь — путь к политике ПДн для ссылки в чекбоксе согласия (блок 2), в #2884 — короткая оговорка под диапазоном цены (блок 4.1). Обе константы живут в одном модуле-без-импортов, шапка объединена. |
||
| 1d7702a862 |
feat(mera): «Ориентировочный расчёт. Не является официальной оценкой.» под диапазоном цены (блок 4.1) (#2884)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
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 2m49s
Deploy Trade-In / deploy (push) Successful in 1m7s
|
|||
|
|
74e7969ef9 |
fix(tradein/lead): кликабельная ссылка на политику ПДн в чекбоксе согласия
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
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 1m6s
CI Trade-In / backend-tests (pull_request) Successful in 4m31s
РКН/владелец: рядом с чекбоксом согласия должна быть ссылка на сам документ
политики обработки ПДн, а не упоминание закона. Чекбокс в LeadForm.tsx
(v2, живой /trade-in/v2) теперь линкует "Политикой обработки персональных
данных" на /mera-public/privacy (target=_blank, чтобы не терять заполненную
форму). Путь вынесен в новый src/lib/legal-copy.ts (модуль без импортов) —
content.ts ре-экспортирует оттуда, чтобы B2B-виджет не тянул B2C-лэндинг-модуль
целиком.
_CONSENT_TEXT_SNAPSHOT/_CONSENT_POLICY_VERSION в lead.py обновлены под новый
плоский текст и дату утверждения политики (PRIVACY_APPROVAL: 2026-08-13).
test_consent_text_frontend_sync.py: экстрактор теперь снимает JSX-теги/{" "}
спейсеры перед сравнением (иначе сломался бы на разметке ссылки) + новый тест
держит _CONSENT_POLICY_VERSION в синхроне с PRIVACY_APPROVAL из content.ts,
чтобы версия не расходилась молча с редакцией документа.
Легаси-дубль в HeroTransparency.tsx (недостижим с живого роута) — текст
приведён в соответствие без ссылки: компонент не смонтирован нигде, и нет
теста, который держал бы там ссылку в актуальном состоянии.
|
||
|
|
91732d1acb |
feat(mera): формулировки шапки и заголовка вкладки по юр-требованиям (блок 3)
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 / 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 56s
Юр-документ владельца от 14.08.2026, блок 3: верхний дескриптор и заголовок вкладки должны уводить сервис от слова, отсылающего к отчёту оценщика по 135-ФЗ, и явно называть основание расчёта — рыночные данные. - Шапка лэндинга: «Оценка вторичного жилья · <регион>» → «Оценка вторичного жилья по рыночным данным · <регион>». Слово «МЕРА» остаётся вордмарком слева (у него свой letter-spacing), строка складывается из двух узлов; регион по-прежнему из REGION_NAME, не литералом. - Заголовок вкладки: «МЕРА — оценка квартиры на вторичном рынке» → «Мера · расчёт стоимости квартиры по рыночным данным». H1 первого экрана («Сколько на самом деле стоит ваша квартира») НЕ меняется — решение владельца: он не заявляет ничего про официальную оценку, а продающий заголовок терять незачем. Заголовки трёх юридических подстраниц живут по своему шаблону «<Документ> — МЕРА» и не затронуты. Проверено: tsc --noEmit, next lint, mera-public isolation guard (18 файлов). |
||
|
|
1b7e047210 |
feat(tradein/estimate): юр-строка «Ориентировочный расчёт. Не является официальной оценкой.»
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / 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 / 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 1m3s
Блок 4.1 юр-требований владельца (14.08.2026): под диапазоном цены на экране результата обязана быть эта строка. Вынесена в новый модуль lib/legal-copy.ts (SHORT_ESTIMATE_DISCLAIMER, без импортов) — используется и в v2/ResultPanel.tsx (боевой экран /v2), и в HeroSummary.tsx (legacy-контур /trade-in/ui-preview/estimate), первым предложением в уже существующем абзаце-дисклеймере про рыночный разброс. В ResultPanel.tsx подрезаны lineHeight/marginTop/padding соседнего блока, чтобы новая строка не сжимала плитки "ИСТОЧНИКИ ДАННЫХ" на фиксированной высоте артборда. |
||
|
|
c6243b166f |
feat(mera): оферта, политика возврата и утверждённая политика ПДн + реквизиты
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / 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 / 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 56s
Юридический блокер публичного B2C-запуска (G6 в mera-b2c-paid-flow-decision.md: `LEGAL_ENTITY == null` → нет реквизитов, нет продажи) и входное требование модерации эквайера: оферты не было вообще, а privacy-страница в собственной шапке писала, что она НЕ утверждённая политика по ст. 18.1 152-ФЗ. Что сделано: - content.ts: `LEGAL_ENTITY` заполнен (ООО «ПРОЕКТ ФЛЭТ», ИНН/КПП/ОГРН, адреса, директор, банковские реквизиты), добавлены `SUPPORT_EMAIL`, `SERVICE_PRICE_RUB`, пути и короткие публичные URL документов. Единый источник — подвал, оферта, возврат и ПДн рендерят эти поля, а не повторяют строки. ОГРН сверен с открытыми данными ЕГРЮЛ (в исходном сообщении владельца был с опечаткой …028028, верный …028281). - Новые страницы /mera-public/oferta и /mera-public/refund — редакции владельца от 13.08.2026 дословно, плейсхолдер `[адрес электронной почты]` заменён на support@meraocenka.ru. - privacy/page.tsx переписана на утверждённую редакцию с оператором и реквизитами приказа. Сохранены оба инварианта честности: раздел про страницу ввода адреса условен по `PUBLIC_ESTIMATE_ENABLED` (п. 5.4 — пока публичный расчёт выключен, адрес не покидает браузер), срок хранения оплаченного отчёта рендерится из `PAID_REPORT_RETENTION_MONTHS`, а не числом в тексте (test_paid_retention_text_consistency.py). - Caddyfile: короткие адреса /oferta, /refund, /privacy → rewrite на поддерево лэндинга. Именно они напечатаны внутри документов и уйдут в заявку эквайеру. Пути перечислены поимённо — allowlist-by-default периметра не ослаблен. - smoke-mera-perimeter.sh: три новые проверки на короткие адреса. Публикация оферты НЕ включает приём оплаты: платёжного контура в коде нет, `PUBLIC_ESTIMATE_ENABLED` по-прежнему false. Оферта публикуется раньше кнопки намеренно — без неё эквайер не примет заявку. Проверено локально: tsc, next lint, vitest (29), isolation guard, next build (три страницы пререндерены), pytest test_paid_retention_text_consistency (3), caddy validate + adapt (rewrite на месте), рендер всех трёх страниц через next start — реквизиты, почта и цена на месте. |
||
| 9e83eb4a53 |
fix(tradein/ui): этаж сделки скрывался у всех 96 974 — требовалось второе поле (#2674) (#2861)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m16s
Deploy Trade-In / deploy (push) Successful in 1m7s
|
|||
| e59a102b16 |
fix(tradein/dkp): «ФАКТИЧЕСКИЕ СДЕЛКИ» называют свой возраст, а не окно поиска (#2847)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m13s
Deploy Trade-In / test (push) Successful in 3m33s
Deploy Trade-In / build-backend (push) Successful in 1m39s
Deploy Trade-In / deploy (push) Successful in 7m1s
|
|||
| 82d8db9f42 |
fix(tradein/estimate): оживлять мёртвые сохранённые оценки при открытии (#2826)
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 3m11s
Deploy Trade-In / test (push) Successful in 3m42s
Deploy Trade-In / build-backend (push) Successful in 1m8s
Deploy Trade-In / deploy (push) Successful in 6m59s
|
|||
| 8423af5dd5 |
feat(tradein): версионирование продукта — единый источник, подвал, PDF, /versions (#2824)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Successful in 36s
Deploy Trade-In / build-frontend (push) Successful in 2m23s
Deploy Trade-In / test (push) Successful in 3m14s
Deploy Trade-In / build-backend (push) Successful in 4m19s
Deploy Trade-In / deploy (push) Successful in 1m44s
|
|||
| 9d9457f67d |
fix(tradein/estimate): не блокировать оценку — расширять подбор и честно предупреждать (#2823)
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m30s
Deploy Trade-In / test (push) Successful in 3m14s
Deploy Trade-In / build-backend (push) Successful in 7m24s
Deploy Trade-In / deploy (push) Successful in 1m25s
|
|||
|
|
7431615415 |
Merge remote-tracking branch 'forgejo/main' into feat/tradein-paid-retention
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m3s
CI Trade-In / backend-tests (pull_request) Successful in 3m57s
# Conflicts: # tradein-mvp/backend/data/sql/_manifest_applied.txt # tradein-mvp/frontend/src/components/trade-in/v2/fixtures.ts |
||
|
|
e6591a450a |
fix(tradein/payments): миграция 234 → 240 — номер снова занят на main
main уехал вперёд за сутки: 234 занял 234_scrape_runs_ban_kind_unknown.sql
(
|