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). Обе константы
живут в одном модуле-без-импортов, шапка объединена.
This commit is contained in:
bot-backend 2026-08-14 15:41:16 +03:00
commit f1491068f4
5 changed files with 82 additions and 21 deletions

View file

@ -4,6 +4,14 @@
* Логотип НЕ является ссылкой на "/": в next.config.ts стоит redirect "/" "/v2",
* то есть клик по нему выкинул бы публичного посетителя в закрытое B2B-приложение
* (и дальше на /login). Пока публичная страница одна, вордмарк просто текст.
*
* ФОРМУЛИРОВКА ДЕСКРИПТОРА (14.08.2026, юр-документ владельца, блок 3). Строка
* шапки обязана читаться как «МЕРА · Оценка вторичного жилья по рыночным данным ·
* <регион>»: слова «по рыночным данным» то, чем сервис отличается от оценки по
* 135-ФЗ, и юрист попросил, чтобы это стояло в самом заметном дескрипторе, а не
* только в дисклеймерах внизу. «МЕРА» приходит из вордмарка ниже (у него свой
* letter-spacing), поэтому в headerTag её нет строка складывается из двух узлов.
* Регион из REGION_NAME, не строкой (правило content.ts).
*/
import { REGION_NAME } from "../content";
@ -18,7 +26,7 @@ export function SiteHeader() {
МЕРА
</div>
<p className={styles.headerTag}>
Оценка вторичного жилья · {REGION_NAME}
Оценка вторичного жилья по рыночным данным · {REGION_NAME}
</p>
</div>
</header>

View file

@ -46,8 +46,16 @@ const plexMono = IBM_Plex_Mono({
display: "swap",
});
/**
* ЗАГОЛОВОК ВКЛАДКИ (14.08.2026, юр-документ владельца, блок 3). «Расчёт
* стоимости», а не «оценка квартиры»: заголовок вкладки второе после шапки
* место, где формулировка читается как заявление о характере услуги, и юрист
* попросил увести её от слова, отсылающего к отчёту оценщика по 135-ФЗ.
* Заголовки трёх юридических подстраниц (оферта / возврат / ПДн) живут по
* своему шаблону «<Документ> МЕРА» и этой правкой не затрагиваются.
*/
export const metadata: Metadata = {
title: "МЕРА — оценка квартиры на вторичном рынке",
title: "Мера · расчёт стоимости квартиры по рыночным данным",
description: `Узнайте рыночную стоимость квартиры по адресу: сделки Росреестра и объявления площадок. ${REGION_NAME}.`,
robots: { index: false, follow: false },
};

View file

@ -5,6 +5,9 @@
/**
* HeroSummary Секция 1 «Сводка» из mockup tradein.html.
* Показывает медиану + достоверность CV + параметры объекта + 2 ценовых бара (объявления / сделки).
*
* hero-duo__hint под диапазоном сделок несёт юр-требование владельца от
* 14.08.2026 (блок 4.1): SHORT_ESTIMATE_DISCLAIMER см. `lib/legal-copy.ts`.
*/
import { useState } from "react";
import type { AggregatedEstimate, TradeInEstimateInput, HouseType, RepairState, ConfidenceLevel } from "@/types/trade-in";
@ -12,6 +15,7 @@ import { asHouseType, asRepairState } from "@/types/trade-in";
import { useActiveBrandSlug, useBrand } from "@/lib/useBrand";
import { sourceDot, sourceLabel } from "@/lib/source-registry";
import { dealsAsOfLabel } from "@/lib/rosreestr";
import { SHORT_ESTIMATE_DISCLAIMER } from "@/lib/legal-copy";
import { HeroTransparency } from "./HeroTransparency";
interface Props {
@ -478,6 +482,9 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals
</div>
)}
<p className="hero-duo__hint">
{/* Блок 4.1 (владелец, 14.08.2026) юр-строка идёт первым
предложением абзаца-дисклеймера, не отдельным блоком. */}
{SHORT_ESTIMATE_DISCLAIMER}{" "}
Диапазон отражает разброс цен по рынку, а не погрешность оценки.
{/* #2209: честное раскрытие точности замеренное покрытие бэктестом
по реальным ДКП Росреестра (2026-07: n=276, coverage 81.5%).

View file

@ -1,9 +1,14 @@
"use client";
/**
* §1.2-дисклеймер под ценовыми карточками несёт юр-требование владельца от
* 14.08.2026 (блок 4.1): SHORT_ESTIMATE_DISCLAIMER см. `lib/legal-copy.ts`.
*/
import { Fragment } from "react";
import type { Ref } from "react";
import { tokens } from "./tokens";
import type { ResultPanelData } from "./mappers";
import { SHORT_ESTIMATE_DISCLAIMER } from "@/lib/legal-copy";
const {
accent,
@ -557,20 +562,29 @@ export default function ResultPanel({
})}
</div>
{/* §1.2 честный разброс: диапазоны цен = рыночный спред, НЕ погрешность
оценки. Ставим сразу под ценовыми карточками, чтобы широкий диапазон
не читался как «неуверенность расчёта». */}
{/* §1.2 честный разброс + юр-дисклеймер (владелец, 14.08.2026, блок 4.1):
SHORT_ESTIMATE_DISCLAIMER идёт первым предложением, "разброс цен"
вторым, один абзац (три отдельных оговорки подряд читались бы
как шум). Ставим сразу под ценовыми карточками, чтобы широкий
диапазон не читался как «неуверенность расчёта».
Компенсация высоты: этот блок стал на строку выше, чем до
SHORT_ESTIMATE_DISCLAIMER (776px средняя колонка узкая для двух
предложений в одну строку) lineHeight/marginTop подрезаны, и
padding у "ranges + radar" box ниже уменьшен по вертикали, чтобы
не сжимать плитки "ИСТОЧНИКИ ДАННЫХ" (flex:1 ниже). Высота
артборда (1536×1024, v2/page.tsx) не менялась. */}
<div
style={{
// M3 — было 10px muted-микропринт; повышено до 12px body, чтобы
// ключевая honest-оговорка о разбросе читалась, а не терялась.
fontSize: 12,
lineHeight: 1.5,
lineHeight: 1.4,
color: body,
marginTop: -4,
marginTop: -6,
}}
>
Диапазоны показывают разброс цен на рынке, а не погрешность оценки.
{SHORT_ESTIMATE_DISCLAIMER} Диапазоны показывают разброс цен на рынке,
а не погрешность оценки.
</div>
{/* ranges + radar */}
@ -581,7 +595,9 @@ export default function ResultPanel({
backdropFilter: "blur(6px)",
border: `1px solid ${line2}`,
borderRadius: 8,
padding: "18px 20px",
// было "18px 20px" — вертикаль подрезана на 4px сверху/снизу, см.
// комментарий к дисклеймеру выше (компенсация высоты под #4.1).
padding: "14px 20px",
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 24,

View file

@ -1,19 +1,30 @@
/**
* Юридические константы, разделяемые между B2C-лэндингом
* Юридические константы и короткие оговорки, разделяемые между B2C-лэндингом
* (`src/app/mera-public/content.ts`) и B2B trade-in виджетами
* (`src/components/trade-in/**`, включая v2 `LeadForm.tsx`).
* (`src/components/trade-in/**`, включая v2 `ResultPanel.tsx` и `LeadForm.tsx`).
*
* ПОЧЕМУ отдельный модуль: v2-компонент результата оценки (B2B-экран,
* пилоты/менеджеры) не должен тянуть `mera-public/content.ts` целиком
* тот модуль набит B2C-лэндинг-специфичными константами (`LEGAL_ENTITY`,
* `PUBLIC_ESTIMATE_ENABLED`, банковские реквизиты и т.д.), появление
* которых в графе импортов B2B-экрана было бы случайной и нежелательной
* связью двух разных поверхностей продукта. Вместо этого путь к странице
* политики ПДн живёт здесь в модуле БЕЗ импортов, который могут
* безопасно использовать обе стороны.
* Источник требований: юр-документ владельца от 14.08.2026 блок 4.1 (строка
* под диапазоном цены) и блок 2 (ссылка на политику ПДн рядом с чекбоксом
* согласия).
*
* `content.ts` ре-экспортирует `PRIVACY_PATH` отсюда (не дублирует
* литерал) единственный источник истины для пути.
* ПОЧЕМУ отдельный модуль: v2-компоненты (B2B-экран, пилоты/менеджеры) не
* должны тянуть `mera-public/content.ts` целиком тот модуль набит
* B2C-лэндинг-специфичными константами (`LEGAL_ENTITY`, банковские реквизиты,
* `PUBLIC_ESTIMATE_ENABLED`), появление которых в графе импортов B2B-экрана
* было бы случайной и нежелательной связью двух поверхностей продукта.
*
* Модуль НАМЕРЕННО без импортов (кроме типов) его тянут и закрытый контур, и
* `/mera-public`, где действует isolation-guard
* (`scripts/check-mera-public-isolation.mjs`): любой транзитивный импорт
* закрытого контура (useMe / lib/api / lib/sessionId / isPathAllowed) в
* публичный бандл провалит проверку.
*
* В репозитории уже есть соседние, но НЕ идентичные дисклеймеры не путать и
* не плодить пятый/шестой литерал с той же сутью:
* - `src/app/mera-public/content.ts` DELIVERABLES_DISCLAIMER (135-ФЗ)
* - `src/app/mera-public/_components/SiteFooter.tsx`
* - `src/app/mera-public/oferta/page.tsx`
* - `backend/app/services/exporters/trade_in_pdf.py` (версия для PDF-отчёта)
*/
/**
@ -22,5 +33,16 @@
* Публично та же страница доступна коротким адресом meraocenka.ru/privacy
* (см. `PUBLIC_PRIVACY_URL` в `mera-public/content.ts`), но для ссылки из
* чекбоксов согласия внутри приложения нужен именно внутренний путь.
*
* `content.ts` ре-экспортирует эту константу (не дублирует литерал)
* единственный источник истины для пути.
*/
export const PRIVACY_PATH = "/mera-public/privacy";
/**
* Строка, которая обязана стоять непосредственно под диапазоном цены на экране
* результата и в боевом `/v2` (`v2/ResultPanel.tsx`), и в превью-контуре
* (`HeroSummary.tsx`, `/trade-in/ui-preview/estimate`).
*/
export const SHORT_ESTIMATE_DISCLAIMER =
"Ориентировочный расчёт. Не является официальной оценкой.";