From 2e82b0810f6b1de3749dca4f492dc89ad7574f04 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 29 Aug 2026 20:28:10 +0500 Subject: [PATCH] =?UTF-8?q?fix(mera/b2c):=20=D1=81=D0=BD=D1=8F=D1=82=D1=8C?= =?UTF-8?q?=20=D1=81=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=8B=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=B5=D1=89=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D1=82=D1=8C=20=D0=B2=D0=B5=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B8=D0=BD=D1=8B=20=D0=B8=D0=B7=D0=BC=D0=B5=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit «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 дн»). --- .../__tests__/landing-v3-render.test.tsx | 41 ++++++++++++++- .../mera-public/_components/InnerHeader.tsx | 2 +- .../_components/estimate/ReportResult.tsx | 37 +++++++++---- .../_components/v3/CostOfErrorV3.tsx | 9 +++- .../_components/v3/FreeCheckCard.tsx | 10 +++- .../_components/v3/FreeResultV3.tsx | 11 ++-- .../mera-public/_components/v3/HeaderV3.tsx | 2 +- .../app/mera-public/_components/v3/HeroV3.tsx | 52 ++++++++++++++----- .../_components/v3/ObjectionsV3.tsx | 2 +- .../mera-public/_components/v3/StepsV3.tsx | 6 +-- .../_components/v3/StickyCtaV3.tsx | 12 ++++- .../mera-public/_components/v3/TwoPathsV3.tsx | 9 +++- .../mera-public/articles/articles-content.ts | 4 +- .../articles/kak-ocenit-kvartiru/page.tsx | 2 +- .../src/app/mera-public/articles/page.tsx | 2 +- .../src/app/mera-public/landing-facts.ts | 21 ++++++-- 16 files changed, 173 insertions(+), 49 deletions(-) diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx index 51c6559f..ed59741d 100644 --- a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx @@ -11,6 +11,7 @@ import { describe, expect, it } from "vitest"; import { AccuracyV3 } from "../_components/v3/AccuracyV3"; import { CostOfErrorV3 } from "../_components/v3/CostOfErrorV3"; +import { HeroV3 } from "../_components/v3/HeroV3"; import type { LandingStat, ShowcaseResponse } from "../public-api"; const stat = (value: number, sample_n: number | null, note: string): LandingStat => ({ @@ -28,6 +29,16 @@ const STATS = { price_cut_median_pct_per_month: stat(-2.17, 3018, "Медианное изменение цены за 30 дней среди снижавших"), }; +/** Hero печатает ещё и медиану аналогов — величину БЕЗ фильтра по городу. */ +const HERO_STATS = { + ...STATS, + analogs_median: stat( + 14, + 1123, + "Медиана числа аналогов на расчёт (только расчёты, где аналоги нашлись)", + ), +}; + const SHOWCASE: ShowcaseResponse = { computed_at: "2026-08-29T03:00:00+00:00", deals: [ @@ -88,7 +99,35 @@ describe("витрина лэндинга v3 без данных", () => { it("«Цена ошибки»: без /stats остаётся только цена — она из оферты, а не из замера", () => { render(); - expect(screen.queryByText(/столько продавцов снижают цену/)).toBeNull(); + expect(screen.queryByText(/столько объявлений на Домклике снижали цену/)).toBeNull(); expect(screen.getByText("150 ₽")).toBeTruthy(); }); + + /** + * Величина не должна стоять под подписью, к которой не привязана. Экспозиция + * считается с фильтром по городу (`_LISTING_AGE_SQL`), медиана аналогов — по + * всей таблице `trade_in_estimates`; под общей шапкой «пример по рынку + * Екатеринбурга» вторая была бы подписана чужой границей. Проверяем не + * наличие двух строк, а СОДЕРЖИМОЕ каждой: слить их обратно в одну и + * оставить тест зелёным невозможно. + */ + it("HeroV3: аналоги подписаны своей границей и своей оговоркой, а не Екатеринбургом", () => { + render(); + const ekbLine = screen.getByText(/Пример по рынку Екатеринбурга/).parentElement; + expect(ekbLine?.textContent).toMatch(/активное объявление висит\s+26\s+дн\./u); + expect(ekbLine?.textContent, "экспозиция и аналоги снова в одной строке").not.toMatch(/аналог/); + + const allLine = screen.getByText(/По всем расчётам МЕРЫ/).parentElement; + expect(allLine?.textContent).toMatch(/медиана аналогов на расчёт/); + // Отбор выживших: расчёты без аналогов в знаменатель не попали. + expect(allLine?.textContent, "note бэкенда выброшен").toMatch( + /только расчёты, где аналоги нашлись/, + ); + }); + + it("HeroV3: без /stats обе строки примера исчезают, а не показывают подпись без числа", () => { + render(); + expect(screen.queryByText(/Пример по рынку Екатеринбурга/)).toBeNull(); + expect(screen.queryByText(/По всем расчётам МЕРЫ/)).toBeNull(); + }); }); diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx index 398f140a..be973f8f 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx @@ -8,7 +8,7 @@ import { PublicLink } from "./PublicLink"; * (`v3/HeaderV3.tsx`). * * Из неё убраны навигация по секциям лэндинга, селектор города и CTA - * «Проверить за 30 секунд». Причина у всех трёх одна: человек уже ушёл с + * «Проверить квартиру». Причина у всех трёх одна: человек уже ушёл с * лэндинга. Якоря вроде `#accuracy` тут просто не на что навести (секций нет * на этих страницах), город выбирается прямо в форме проверки — а второй * селектор города в шапке означал бы два места, задающих одно значение. diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx index 13dbf946..8e9d73c4 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx @@ -23,11 +23,24 @@ * «возраст активного объявления», а НЕ макетное «срок продажи по дому» — * выборка цензурирована, честное имя величины важнее красоты. * - * Возврат 150 ₽ при ненадёжном расчёте — политика с /refund; в тизере она + * Возврат при ненадёжном расчёте — политика с /refund; в тизере она * упоминается той же строкой, что в макете, со ссылкой на полный текст. + * + * ЦЕНА — ТОЛЬКО `SERVICE_PRICE_RUB`. Здесь оставались три литерала «150 ₽» + * (плашка тизера, текст и строка возврата). Цена стоит в оферте и в политике + * возврата; вписанная в экран копия разъехалась бы с ними молча, и человек + * увидел бы одну цифру на витрине, а другую в документе, который его + * обязывает. + * + * СРОК. Строка тизера обещала «прогноз срока продажи»: платный расчёт отдаёт + * `est_days_on_market` — медиану `days_on_market` аналогов, то есть сколько + * похожие объявления УЖЕ висят. `deals.days_on_market` заполнен 0 раз из + * 108 623, поэтому сверить прогноз срока не с чем и прогнозом эта величина + * не является. Названа тем, что считается, — ровно как в пункте 5 выше про + * бесплатные плитки. */ -import { SUPPORT_TELEGRAM_LABEL, SUPPORT_TELEGRAM_URL } from "../../content"; +import { SERVICE_PRICE_RUB, SUPPORT_TELEGRAM_LABEL, SUPPORT_TELEGRAM_URL } from "../../content"; import type { CoverageVerdict } from "../../coverage-copy"; import styles from "../../landing-v3.module.css"; @@ -129,7 +142,7 @@ function FreeCards({ verdict }: { verdict: CoverageVerdict }) { const PAID_ROWS = [ { label: "РЕКОМЕНДОВАННАЯ ЦЕНА", w: "60%" }, - { label: "ПРОГНОЗ СРОКА ПРОДАЖИ ПРИ ЭТОЙ ЦЕНЕ", w: "45%" }, + { label: "СКОЛЬКО УЖЕ ВИСЯТ ПОХОЖИЕ ОБЪЯВЛЕНИЯ", w: "45%" }, { label: "ДИАПАЗОН И ПОЛОЖЕНИЕ НА РЫНКЕ", w: "70%" }, { label: "АНАЛОГИ С АДРЕСАМИ И ЦЕНАМИ", w: "88%" }, ] as const; @@ -139,7 +152,9 @@ function PaidTeaser() { return (
- ПОЛНЫЙ ОТЧЁТ · 150 ₽ + + ПОЛНЫЙ ОТЧЁТ · {SERVICE_PRICE_RUB} ₽ +
@@ -159,17 +174,17 @@ function PaidTeaser() {
- Цена и срок продажи — следующий шаг + Ваша цена — следующий шаг

- Полный расчёт стоит 150 ₽: диапазон цены, цена за квадратный - метр и список квартир, на которых он построен. Один платёж, без - подписки. Приём оплаты сейчас подключается — как только заработает, - кнопка появится здесь. + Полный расчёт стоит {SERVICE_PRICE_RUB} ₽: диапазон цены, цена + за квадратный метр и список квартир, на которых он построен. Один + платёж, без подписки. Приём оплаты сейчас подключается — как только + заработает, кнопка появится здесь.

- Если надёжный расчёт не получится — вернём 150 ₽:{" "} - условия возврата. + Если надёжный расчёт не получится — вернём {SERVICE_PRICE_RUB} +  ₽: условия возврата.

diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx index 1421ad1d..c90201f6 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx @@ -9,6 +9,13 @@ * снижал»: одна подпись на две величины была бы подменой знаменателя. * Цена — `SERVICE_PRICE_RUB` (content.ts), тот же источник, что у оферты. * + * ОБЪЯВЛЕНИЯ, А НЕ ПРОДАВЦЫ. `price_cut_share_pct` — доля ОБЪЯВЛЕНИЙ, и + * только по Домклику (единственный источник, где триггер пишет стартовую + * цену, см. шапку `landing_stats.py`). Один продавец ведёт несколько + * объявлений, поэтому «столько продавцов снижают цену» приписывало величине + * единицу, которой её не мерили: подпись приведена к измеренному, а источник + * назван прямо в ней, а не только в `note` под плиткой. + * * ЧЕГО ЗДЕСЬ БОЛЬШЕ НЕТ. Плитки «×2,4 дольше продаётся квартира с завышенной * ценой»: замер даёт ×1,04 (79 дней против 76), и это уже с цензурой в пользу * заявления. Её место заняла доля снижающих цену — измеренная величина про то @@ -51,7 +58,7 @@ export function CostOfErrorV3({ stats }: { stats: LandingStats }) { tiles.push({ key: "share", value: share.text, - title: "столько продавцов снижают цену", + title: "столько объявлений на Домклике снижали цену", text: [share.note, share.sample].filter(Boolean).join(" · "), }); } diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx index c97ad1ac..bd045ed1 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx @@ -4,7 +4,7 @@ * FreeCheckCard — карточка «Бесплатная проверка» на первом экране (макет v3, * `МЕРА для себя v3.dc.html` строки ~129-170). * - * ЧТО ОНА ДЕЛАЕТ СЕГОДНЯ. Проверяет пять полей и уводит на `/estimate`, где + * ЧТО ОНА ДЕЛАЕТ СЕГОДНЯ. Проверяет шесть полей и уводит на `/estimate`, где * живёт сама проверка. Макетный сценарий «спиннер 1100 мс → готовый * результат прямо в карточке» не воспроизводится и не будет: результату нужно * место рядом с полями, а автокомплиту — место под список; в карточке первого @@ -39,6 +39,11 @@ * не использует (см. `CoverageProbeInput` на бэке), но выбросить уже * полученный ответ и спросить его второй раз на платном шаге — хуже, чем * донести его в `sessionStorage` до того шага. + * + * ПОДПИСЬ ПОД КНОПКОЙ. В ней стояло «30 СЕКУНД» — замера времени заполнения + * формы нет и не было. Его место занял `STEP1_FIELDS_LABEL`: число полей — + * проверяемый факт, и оно приезжает из `landing-facts.ts`, а не вписано сюда + * второй раз (иначе с бейджем шага 1 они разъехались бы молча). */ import { useId, useRef, useState } from "react"; @@ -54,6 +59,7 @@ import { SECONDARY_CITIES, } from "../../content"; import { saveDraft } from "../../estimate-draft"; +import { STEP1_FIELDS_LABEL } from "../../landing-facts"; import styles from "../../landing-v3.module.css"; /** Значение

- Ваша цена и срок продажи готовы + Ваша цена готова

diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx index 3e5ad3a7..42435255 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx @@ -14,6 +14,20 @@ * конкретного человека. Цена — `SERVICE_PRICE_RUB` (content.ts), один * источник с офертой и политикой возврата. * + * ДВЕ СТРОКИ ПРИМЕРА, А НЕ ОДНА. Екатеринбургом ограничена только экспозиция + * (`_LISTING_AGE_SQL` фильтрует `listings.city`); медиана аналогов считается + * по всей таблице `trade_in_estimates` без фильтра по городу. Стоя под общей + * шапкой «пример по рынку Екатеринбурга», она была бы подписана городом, к + * которому не привязана, — поэтому у каждой величины своя подпись со своей + * границей. Рядом с аналогами печатается их `note` бэкенда («только расчёты, + * где аналоги нашлись»): это отбор выживших, расчёты без аналогов в + * знаменатель не попали, и без оговорки медиана читается выше, чем есть. + * + * СРОК. В тексте нет «узнаете срок продажи»: платный отчёт отдаёт + * `est_days_on_market` — медиану `days_on_market` аналогов, то есть сколько + * похожие объявления УЖЕ висят (`deals.days_on_market` пуст, сверять прогноз + * срока не с чем). Обещание приведено к этой величине. + * * ЧЕГО В ТЕКСТЕ БОЛЬШЕ НЕТ: обещания «каждый такой прогноз мы потом сверяем с * фактом сделки». Контура сверки прогноза клиента с его сделкой в продукте не * существует (`trade_in_leads` — 4 строки без полей исхода), и обещать его @@ -53,12 +67,22 @@ export function HeroV3({ stats }: { stats: LandingStats }) { digits: 0, sampleWord: "объявлениям", }); - const marketExample = [ - analogs && `медиана аналогов на расчёт — ${analogs.text}${analogs.sample ? ` (${analogs.sample})` : ""}`, - age && `активное объявление висит ${age.text}${age.sample ? ` (медиана ${age.sample})` : ""}`, - ] - .filter((line): line is string => Boolean(line)) - .join("; "); + const marketLines = [ + age && { + key: "age", + label: "Пример по рынку Екатеринбурга, а не расчёт по вашему адресу:", + text: [`активное объявление висит ${age.text}`, age.sample && `медиана ${age.sample}`] + .filter(Boolean) + .join(" · "), + }, + analogs && { + key: "analogs", + label: "По всем расчётам МЕРЫ, а не только по Екатеринбургу:", + text: [`медиана аналогов на расчёт — ${analogs.text}`, analogs.sample, analogs.note] + .filter(Boolean) + .join(" · "), + }, + ].filter((line): line is { key: string; label: string; text: string } => Boolean(line)); return (
- Не зависнуть: узнаете, за сколько дней уйдёт - квартира по вашей цене + Не зависнуть: увидите, сколько времени уже + висят похожие квартиры рядом
@@ -133,20 +157,20 @@ export function HeroV3({ stats }: { stats: LandingStats }) { За{" "} {`${SERVICE_PRICE_RUB} ₽`}: {" "} - ваша цена, срок продажи и аналоги, на которых построен расчёт + ваша цена, экспозиция похожих объявлений и аналоги, на которых + построен расчёт
- {marketExample !== "" && ( -
+ {marketLines.map((line) => ( +
- Пример по рынку Екатеринбурга, а не расчёт по вашему адресу:{" "} - {marketExample} + {line.label} {line.text}
- )} + ))}
{/* Покрытие подано неравномерно намеренно — см. COVERED_CITIES в diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx index 9b1d736b..e5cc5b1e 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx @@ -135,7 +135,7 @@ export function ObjectionsV3() { Что обычно спрашивают перед оплатой - Проверить за 30 секунд + Проверить квартиру
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx index 9bd6afa0..a5cd6e92 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx @@ -30,11 +30,11 @@ const STEPS_V3: readonly StepV3[] = [ }, { title: "Мы собираем рынок вокруг", - text: "Похожие квартиры рядом: сколько их продаётся сейчас и за сколько дней они уходят.", + text: "Похожие квартиры рядом: сколько их продаётся сейчас и как давно они висят.", }, { - title: "Вы получаете цену и срок", - text: "Конкретная цифра для объявления, прогноз срока продажи и все аналоги, на которых построен расчёт.", + title: "Вы получаете цену и картину рынка", + text: "Конкретная цифра для объявления, сколько уже висят похожие объявления и все аналоги, на которых построен расчёт.", accent: true, }, ] as const; diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx index eb957cf4..0b95e7c4 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx @@ -14,6 +14,14 @@ * компоненте — так `StickyCtaV3` остаётся самодостаточным независимо от * того, куда именно в дерево страницы его вставят (ожидаемое место — сразу * после подвала, как Header/Footer в текущем `layout.tsx`). + * + * НА КНОПКЕ НЕТ СРОКА. Здесь стояло «Проверить за 30 секунд» — замера времени + * заполнения формы нет и не было (та же причина, по которой «30 СЕКУНД» ушло + * из бейджа первого шага, см. `landing-facts.ts`). Панель висит на экране + * ВСЕГДА, поэтому именно с неё обещание, которого никто не мерил, читалось + * чаще всего. В тексте панели по той же причине нет и «срока продажи»: + * платный отчёт отдаёт медиану экспозиции похожих объявлений, а не прогноз + * срока (`_estimate_days_on_market`, см. HeroV3). */ import styles from "../../landing-v3.module.css"; @@ -26,10 +34,10 @@ export function StickyCtaV3() { diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx b/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx index 646bdcc1..cee546f8 100644 --- a/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx @@ -88,7 +88,7 @@ export default function ArticlesPage() {

- Проверить за 30 секунд + Проверить квартиру diff --git a/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts b/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts index b718b2c1..a73b1bb2 100644 --- a/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts +++ b/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts @@ -87,8 +87,21 @@ export const BACKTEST_CORRIDOR_LABEL = "±37 %"; export const BACKTEST_PERIOD_LABEL = "сделки с июня 2025 года"; /** - * Бейдж первого шага. Раньше здесь стояло «30 секунд» — замера времени - * заполнения формы нет и не было. Число полей формы, наоборот, проверяется - * глазами в `FreeCheckCard.tsx`: адрес, площадь, комнаты, этаж, состояние. + * Бейдж первого шага И подпись под кнопкой формы (`FreeCheckCard.tsx`). + * Раньше в обоих местах стояло «30 секунд» — замера времени заполнения формы + * нет и не было. Число полей формы, наоборот, проверяется глазами там же: + * город, адрес, площадь, комнаты, этаж, состояние. + * + * ШЕСТЬ, А НЕ ПЯТЬ. Пока эта подпись делалась, соседняя ветка вернула в форму + * ВЫБОР ГОРОДА (#2576: без него «Ленина, 1» давало жителю Нижнего Тагила цену + * по одноимённой улице в ЕКБ). Число полей — не константа продукта, а счёт + * того, что стоит в разметке рядом; после слияния двух веток «5 полей» было бы + * ровно тем враньём на витрине, ради снятия которого подпись и заводилась. + * Меняется форма — пересчитать здесь; проверяется по `heroFieldLabel` в + * `FreeCheckCard.tsx`. + * + * Оба места берут строку ОТСЮДА, а не пишут её у себя: «30 секунд» пережило + * снятие из бейджа ровно потому, что было вписано в четыре компонента + * независимо, и правка одного не касалась остальных. */ -export const STEP1_FIELDS_LABEL = "5 полей"; +export const STEP1_FIELDS_LABEL = "6 полей";