trade-in/v2: UI/UX аудит — round 2 (системный контраст · честность чисел: дедуп/фильтр/адрес · a11y drawer+автокомплит) #2081

Open
opened 2026-06-28 20:50:46 +00:00 by lekss361 · 4 comments
Owner

Повторный экспертный аудит живого /trade-in/v2 (МЕРА) под pilottest на реальной оценке (Белинского 86, 54 м², 2-к, ?id=2314f87b…), 4 линзы. Дочерняя к эпику #2036, продолжение #2062 (первый раунд). Находки подтверждены живьём (Playwright: скрин / axe-инъекция / DOM-замер) и заземлены в код (file:line) через grounding-проход по tradein-mvp/frontend/src/components/trade-in/v2/*.

Архитектура (не путать): прод обслуживает tradein-mvp/frontend (Next 15, basePath=/trade-in), НЕ главный frontend/. Цифры/формат — в mappers.ts; токены — tokens.ts.

Вердикт: раунд #2062 (#2066/2069/2072/2076/2078/2079) подтверждён живьём — продукт стал крепким и честным по замыслу. До «платного» не хватает: (1) системно дотемнить контраст микро-типографики (главный незакрытый дефект, невидим их же axe-гейту), (2) убрать противоречия в числах (косметический фильтр «2-к», дедуп кросс-постинга, обрезка дома в адресе, рассинхрон счётчиков/процентов с бэком), (3) усилить иерархию 3 цен, (4) добить a11y хвост (LocationDrawer / автокомплит / div-таблицы). Happy-path не сломан.


Подтверждено живьём как ПОЧИНЕННОЕ (#2062 — анти-регресс)

a11y-форма (КОМНАТ/ТИП/РЕМОНТ/РАДИУС = role=combobox+listbox+aria-activedescendant; БАЛКОН = role=radiogroup+aria-checked; label[for]; Esc закрывает оверлей + возвращает фокус на nav-таб) · CTA залит brand-blue · scale-fit (transform scale(0.75@1366 / 0.879@1440), overflowX=0, правый столбец виден) · оверлеи = role=dialog aria-modal aria-labelledby · КОЭФ.ЛОКАЦИИ→«скоро» · «ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ · с учётом торга» · footer-провенанс из data («ПОСТРОЕНО ПО 14 АНАЛОГАМ И 42 СДЕЛКАМ») · даты ДКП «янв 2026» · building.png грузится (304) · контраст source-бейджей в оверлеях (commit 848a44c6 уже в main, SourcesView.tsx:99-101body2 6.3:1). axe: 0 non-contrast violations во всех состояниях.


🔴 CRITICAL

C1 · [global/tokens] Системный недобор контраста микро-типографики (WCAG 1.4.3), невидимый их axe-гейту.
Что: 9–11px лейблы/подписи по ВСЕМ экранам ниже AA на светлом холсте: muted2 #91a1b4=2.09:1 (поля формы, заголовки карточек, ₽/м²), muted3 #9aaabb=1.88:1 (eyebrow «ИТОГИ ПО РАЗДЕЛАМ», «нет данных»), muted4 #b3c0cf=1.47:1 («если знаете»/«опц.»/«скоро»/v2.0.6), hint #8a9aac=2.28:1 (helper), muted #6f8195=3.17:1 (КАК РАССЧИТАНО, НЕТ/ДА, плейсхолдеры, «← К ОЦЕНКЕ»). Даже против самого светлого стопа холста #f4f8fc — fail.
→ Фикс (1 файл-контракт): tokens.ts:27-31 дотемнить шкалу muted; для ПЕРВИЧНЫХ лейблов перевести muted2/muted3 → body2 #46586c / ink2, muted4 оставить только для истинных hint. Использование: ResultPanel.tsx:175-178,226, ObjectSummary.tsx:110-116, ParamsPanel.tsx:305-306, Footer.tsx:120, TopNav.tsx:169,303,311, LocationDrawer.tsx:149,184, SectionOverlay.tsx:217, + AnalyticsView/HistoryView/CacheView (пер­вазивно).
Почему CRITICAL: каждый экран, каждый лейбл; axe слеп — blueprint-grid = background-image (tokens.ts:109-110) → все 74 contrast-проверки уходят в incomplete, не violations (axe=0 обманчив). Усугубляется downscale: 9px×0.75 ≈ 6.75px физических на 1366. Пруф: ручной computed-color sweep (44→76 фейлов) + axe-incomplete дамп + tokens.ts.


🟠 HIGH

H1 · [SourcesView] Чип-фильтр «Планировка 2-к» — косметический; набор аналогов содержит 1-к/3-к/5-к. (verdict: TRUE)
Живьём: «ОБЪЯВЛЕНИЙ ПО АНАЛОГАМ 14», но строки — 1-к(47м²), 3-к(109/126м²), 5-к(153м² @30,8М) для 54м² 2-к субъекта; диапазон вздут до 11,6–31М.
mappers.ts:1667 чип берётся из e.rooms (субъект), а adRows (mappers.ts:1697-1705) = ВСЕ e.analogs без room-фильтра; массив фильтров — display-only. Фикс: реально фильтровать аналоги по комнатности/площади ИЛИ переименовать чип в честное «выборка по площади 47–153 м²». Пруф: скрин A05 + grounding.

H2 · [ResultPanel] Три равновесные цены без явного «это ответ». (FE)
13,55 / 11,09 / 7,67 млн — разброс 1,77×, одинаковый вес; логический ответ (ожидаемая 11,09) — СРЕДНЯЯ карточка без акцента; ДКП-Росреестр 7,67 (−43%) равноправен → «так 7,67 или 13,55?».
ResultPanel.tsx:143-148 (gridTemplateColumns 1fr 1fr 1fr, идентичные стили). Фикс: визуально выделить «ожидаемую» (крупнее/accent), ДКП-Росреестр демоутить в supporting-метрику. Пруф: скрин A02.

H3 · [mappers] parseAddress() молча обрезает номер дома у адресов без префикса «ул/пр». (verdict-adjacent, root-cause найден)
Hero показывает «Белинского» вместо «Белинского, 86».
mappers.ts:413-450: ищет street-keyword (ул/пр/пер/пл…) по STREET_RE; «Белинского, 86» без «ул» → нет матча → fallback parts[0]="Белинского", дом «86» теряется (то же для «Тверская, 25»→«Тверская»). Фикс: сохранять house-token / fallback на полную строку. Пруф: hero «Белинского» vs input/pin/summary «Белинского, 86».

H4 · [mappers] Нет кросс-source дедупа — кросс-постинг раздувает n аналогов и медиану. (verdict: TRUE; владелец — backend-контракт)
Живьём: один и тот же лот посчитан 3× (80м²/265 000/21,2М: N1+Домклик+Циан) и 2× (67м²/328 283/21,995М: Циан+Домклик). 14 «аналогов» → ~6-7 уникальных.
mappers.ts:478-505 (buildSources считает сырые source'ы), медиана/бины :928-939 из сырого e.analogs; дедуп-ключа (адрес+площадь+этаж+цена) нет — FE предполагает, что API уже дедуплицирован. Фикс: дедуп на бэке (или в mappers по ключу) до счёта/медианы. Связано с #1772 (дробление дома) и honesty-PR #2057.


🟡 MEDIUM

M1 · [data-contract] Счётчик «ИСТОЧНИКОВ X/7» не сходится с залитыми плитками и плавает между загрузками. Живьём: 5/7 сразу после submit → 6/7 после reload того же ?id, при 5 залитых плитках (Авито+Restate=«нет данных»). Verify: логика activeSourceCountbuildSources (mappers.ts:478-505,512-514) ДЕТЕРМИНИРОВАНА (не FE-баг) → причина в данных (источник присутствует в e.analogs, но плитка рисует «нет данных», и/или данные досчитались между загрузками). → свести source_counts бэка с тем, что считают плитки; один канонический счётчик.

M2 · [data] Донат «−16% К РЫНКУ» не совпадает с показанными ценами (−18,2%). 11,09/13,55=0,818; 205 328/250 879=0,818. Verify: формула (asking_to_sold_ratio−1)·100 верна (mappers.ts:835-836) → бэковый asking_to_sold_ratio≈0,84 рассинхронен с показанными медианами. (Сделки «−43%» сходятся: 7,67/13,55.) → согласовать ratio с медианами на бэке.

M3 · [content] «СРЕДНИЙ ТОРГ −2,3%» (Аналитика) противоречит «ожидаемой −16/−18%» — несовместимые базы. Verify: TRUE. mappers.ts:1291 торг из median_bargain_pct (house placement start→last), :835 ожидаемая из asking_to_sold_ratio (estimate) — разные источники/смыслы, не примирены для юзера. → одна согласованная история торга, или подпись баз.

M4 · [HeroBar/Footer] Мёртвые/вводящие в заблуждение контролы в empty/404 (нет отчёта). «СКАЧАТЬ PDF-ОТЧЁТ», «КАК РАССЧИТАНО» (HeroBar.tsx:211-234), «ДЕЙСТВИТЕЛЕН ДО —» (HeroBar.tsx:176, Footer.tsx:39-41, page.tsx:744 рендерит Footer безусловно). → гейтить на наличие estimate (как у PDF-кнопки). Пруф: скрин A10 (404 «ОТЧЁТ НЕ НАЙДЕН», но hero-кнопки и «ДЕЙСТВИТЕЛЕН 24 Ч» на месте).

M5 · [LocationDrawer] Не настоящий dialog + крестик-div. LocationDrawer.tsx:73-99aria-hidden есть, но нет role=dialog/aria-modal/aria-labelledby/focus-trap (TODO P3); закрытие :166 = <div onClick> (не <button>, без aria-label, не с клавиатуры); Esc-хендлер есть (:26-27) но без preventDefault/stopPropagation → в стэке с оверлеем Esc уходит диалогу, drawer живьём не закрылся. → выровнять по SectionOverlay (эталон).

M6 · [ParamsPanel/AddressInput] Автокомплит адреса без combobox-ARIA. #pp-address (ParamsPanel.tsx:1087-1158) рендерит дропдаун подсказок (geocode/suggest=200), но input без role=combobox/aria-expanded/controls/autocomplete/activedescendant, попап без role=listbox, пункты без role=option. Эталон рядом — Dd (:203-276). → обвязать combobox + стрелочную навигацию.

M7 · [a11y] Таблицы div-grid (нет <table>/<th>) + scrollable-region + графики без подписи. SourcesView.tsx:263-277, CacheView.tsx:104-150, HistoryView/AnalyticsView — display:grid+<span>, нет колоночной семантики для SR. axe в Аналитике: scrollable-region-focusable (serious, 1) — скролл-контейнер без tabindex=0. 3 чарт-SVG: только 1 с <title>, без role/aria-label. → семантические row/cell, tabindex=0 на скролл, <title>+aria-label на чарты.

M8 · [CacheView] Колонка ВРЕМЯ мешает формат: только-время (свежие) vs только-дата (устаревшие). CacheView.tsx:118-122 рендерит r.time как есть; нельзя понять, какого дня «23:20:51». → нормализовать форматтером (today→HH:MM, older→DD.MM.YYYY).

M9 · [LocationDrawer] Текст «КАК СЧИТАЕМ» расходится с реальными источниками. LocationDrawer.tsx:198-208: «Авито, Циан, Яндекс, Домклик» — опускает N1.ru и Restate (есть как плитки), зовёт Я.Недвижимость «Яндекс», ставит Авито (=нет данных) первым. → список из реального source-словаря.

M10 · [MapCard] Пин субъекта подписан данными аналога. Тултип «Белинского, 86 / 80 м² / 21,20 млн» = N1.RU-аналог 80м², а субъект — 54м². → пин субъекта показывать площадь/адрес субъекта, аналоги — отдельным маркером.

M11 · [ObjectSummary/SourcesView] Безымянные числа + колонка-прочерк (хвост #2062). «Аналоги 14 · 21,60 млн» ≠ медиана 13,55 (что за 21,60?); колонка ДИСТ. в A05 целиком «—». → подписать значения; скрывать пустые колонки.

M12 · [content] Жаргон на витрине без расшифровки. CV / ДКП / ₽/м² / «с учётом торга» — на главном экране inline-пояснения нет (drawer объясняет только CV). → tooltip/сноска или человеческие формулировки.


🟢 LOW

  • L1 AnalyticsView: «+10% ~237 дн · обычно 237–237 дн · 1 аналог» — фейк-диапазон из n=1 (скрывать CI при n=1).
  • L2 AnalyticsView «История цен · Avito + Яндекс (20 лотов)» — легенда только «Яндекс», Avito=нет данных (убрать пустую серию/поправить подпись).
  • L3 404 шлёт 3 обречённых саб-запроса (placement-history/sell-time/house-analytics) после 404 основного estimate (short-circuit).
  • L4 HistoryView: лот с ценой нач<посл (26,00→27,00) + экспозиция 1067 дн в среднем — выбросы.
  • L5 Табы 04-07 кликабельны до оценки → пустые шеллы (disable+tooltip) — хвост #2062.
  • L6 [process] blueprint-grid как background-image слепит axe (tokens.ts:109-110) — вынести сетку в ::before/SVG-слой позади текста, чтобы contrast стал auditable их же гейтом (иначе регрессии контраста не ловятся CI).
  • L7 Хардкод hex иконок (#6f8195) в TopNav.tsx вместо tokens.muted (maintainability).

Quick-wins (FE-only, делать первыми)

  1. tokens.ts:27-31 — дотемнить muted-шкалу (закрывает C1 + M11/M12-контраст одним файлом; первичные лейблы→body2/ink2).
  2. Гейтить КАК РАССЧИТАНО / СКАЧАТЬ PDF / ДЕЙСТВИТЕЛЕН ДО / Footer на наличие estimate (M4).
  3. Чип «2-к»: реально фильтровать аналоги ИЛИ переименовать (H1).
  4. parseAddress — сохранять номер дома (H3).
  5. Выделить «ожидаемую» карточку, демоутить ДКП-Росреестр (H2).
  6. LocationDrawer → role=dialog + <button aria-label> + focus-trap (M5); автокомплит → combobox-ARIA (M6).
  7. clamp scale ≥0,88 на <1366px, чтобы 9px не падал ниже ~8px (усугубление C1).
  8. tabindex=0 на скролл-контейнер Аналитики (M7, единственный serious-axe).

axe-сводка по состояниям

состояние non-contrast violations contrast
empty 0 74 incomplete (axe слеп — bg-image)
result 0 incomplete
sources (05) 0 incomplete
analytics (06) 1 serious (scrollable-region-focusable) incomplete
dropdown open (listbox) 0 incomplete
404 0 (+4 console resource-404, graceful) incomplete
Везде contrast → incomplete; авторитетен ручной computed-color sweep.

Скрины (gitignored .playwright-mcp/)

A01-empty-full · A02-after-submit (result) · A04-prodazhi-v-dome (04) · A05-sources-overlay (05) · A06b-analytics-clean (06) · A08-predydushie (07) · A09-responsive-1366 · A10-404-badid · A11-autocomplete.

Сильные стороны (НЕ ломать)

HUD/blueprint-система, mono-цифры, модель 3 оценок, ARIA-listbox селекты + radiogroup + Esc/focus-return, scale-fit, честные «нет данных»/«частичный результат не блокируется»/404-vs-generic, КОЭФ.ЛОКАЦИИ «скоро».

Повторный экспертный аудит живого `/trade-in/v2` (МЕРА) под `pilottest` на реальной оценке (Белинского 86, 54 м², 2-к, `?id=2314f87b…`), 4 линзы. Дочерняя к эпику #2036, продолжение #2062 (первый раунд). Находки подтверждены живьём (Playwright: скрин / axe-инъекция / DOM-замер) **и** заземлены в код (`file:line`) через grounding-проход по `tradein-mvp/frontend/src/components/trade-in/v2/*`. **Архитектура (не путать):** прод обслуживает `tradein-mvp/frontend` (Next 15, `basePath=/trade-in`), НЕ главный `frontend/`. Цифры/формат — в `mappers.ts`; токены — `tokens.ts`. **Вердикт:** раунд #2062 (#2066/2069/2072/2076/2078/2079) подтверждён живьём — продукт стал крепким и честным по замыслу. До «платного» не хватает: (1) **системно дотемнить контраст микро-типографики** (главный незакрытый дефект, невидим их же axe-гейту), (2) убрать **противоречия в числах** (косметический фильтр «2-к», дедуп кросс-постинга, обрезка дома в адресе, рассинхрон счётчиков/процентов с бэком), (3) усилить иерархию 3 цен, (4) добить a11y хвост (LocationDrawer / автокомплит / div-таблицы). Happy-path не сломан. --- ## ✅ Подтверждено живьём как ПОЧИНЕННОЕ (#2062 — анти-регресс) a11y-форма (КОМНАТ/ТИП/РЕМОНТ/РАДИУС = `role=combobox`+listbox+`aria-activedescendant`; БАЛКОН = `role=radiogroup`+`aria-checked`; `label[for]`; Esc закрывает оверлей + **возвращает фокус** на nav-таб) · CTA залит brand-blue · scale-fit (transform `scale(0.75@1366 / 0.879@1440)`, `overflowX=0`, правый столбец виден) · оверлеи = `role=dialog aria-modal aria-labelledby` · КОЭФ.ЛОКАЦИИ→«скоро» · «ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ · с учётом торга» · footer-провенанс из data («ПОСТРОЕНО ПО 14 АНАЛОГАМ И 42 СДЕЛКАМ») · даты ДКП «янв 2026» · building.png грузится (304) · **контраст source-бейджей в оверлеях** (commit 848a44c6 уже в `main`, `SourcesView.tsx:99-101` → `body2` 6.3:1). axe: 0 non-contrast violations во всех состояниях. --- ## 🔴 CRITICAL **C1 · [global/tokens] Системный недобор контраста микро-типографики (WCAG 1.4.3), невидимый их axe-гейту.** Что: 9–11px лейблы/подписи по ВСЕМ экранам ниже AA на светлом холсте: `muted2 #91a1b4`=**2.09:1** (поля формы, заголовки карточек, ₽/м²), `muted3 #9aaabb`=**1.88:1** (eyebrow «ИТОГИ ПО РАЗДЕЛАМ», «нет данных»), `muted4 #b3c0cf`=**1.47:1** («если знаете»/«опц.»/«скоро»/v2.0.6), `hint #8a9aac`=2.28:1 (helper), `muted #6f8195`=3.17:1 (КАК РАССЧИТАНО, НЕТ/ДА, плейсхолдеры, «← К ОЦЕНКЕ»). Даже против самого светлого стопа холста `#f4f8fc` — fail. → Фикс (1 файл-контракт): `tokens.ts:27-31` дотемнить шкалу muted; для ПЕРВИЧНЫХ лейблов перевести `muted2/muted3 → body2 #46586c / ink2`, `muted4` оставить только для истинных hint. Использование: `ResultPanel.tsx:175-178,226`, `ObjectSummary.tsx:110-116`, `ParamsPanel.tsx:305-306`, `Footer.tsx:120`, `TopNav.tsx:169,303,311`, `LocationDrawer.tsx:149,184`, `SectionOverlay.tsx:217`, + AnalyticsView/HistoryView/CacheView (пер­вазивно). Почему CRITICAL: каждый экран, каждый лейбл; **axe слеп** — blueprint-grid = `background-image` (`tokens.ts:109-110`) → все 74 contrast-проверки уходят в `incomplete`, не `violations` (axe=0 обманчив). Усугубляется downscale: 9px×0.75 ≈ **6.75px** физических на 1366. Пруф: ручной computed-color sweep (44→76 фейлов) + axe-incomplete дамп + `tokens.ts`. --- ## 🟠 HIGH **H1 · [SourcesView] Чип-фильтр «Планировка 2-к» — косметический; набор аналогов содержит 1-к/3-к/5-к.** (verdict: TRUE) Живьём: «ОБЪЯВЛЕНИЙ ПО АНАЛОГАМ 14», но строки — 1-к(47м²), 3-к(109/126м²), 5-к(153м² @30,8М) для 54м² 2-к субъекта; диапазон вздут до 11,6–31М. → `mappers.ts:1667` чип берётся из `e.rooms` (субъект), а `adRows` (`mappers.ts:1697-1705`) = ВСЕ `e.analogs` без room-фильтра; массив фильтров — display-only. Фикс: реально фильтровать аналоги по комнатности/площади ИЛИ переименовать чип в честное «выборка по площади 47–153 м²». Пруф: скрин `A05` + grounding. **H2 · [ResultPanel] Три равновесные цены без явного «это ответ».** (FE) 13,55 / 11,09 / 7,67 млн — разброс 1,77×, одинаковый вес; логический ответ (ожидаемая 11,09) — СРЕДНЯЯ карточка без акцента; ДКП-Росреестр 7,67 (−43%) равноправен → «так 7,67 или 13,55?». → `ResultPanel.tsx:143-148` (`gridTemplateColumns 1fr 1fr 1fr`, идентичные стили). Фикс: визуально выделить «ожидаемую» (крупнее/accent), ДКП-Росреестр демоутить в supporting-метрику. Пруф: скрин `A02`. **H3 · [mappers] `parseAddress()` молча обрезает номер дома у адресов без префикса «ул/пр».** (verdict-adjacent, root-cause найден) Hero показывает «Белинского» вместо «Белинского, 86». → `mappers.ts:413-450`: ищет street-keyword (`ул/пр/пер/пл…`) по `STREET_RE`; «Белинского, 86» без «ул» → нет матча → fallback `parts[0]="Белинского"`, дом «86» теряется (то же для «Тверская, 25»→«Тверская»). Фикс: сохранять house-token / fallback на полную строку. Пруф: hero «Белинского» vs input/pin/summary «Белинского, 86». **H4 · [mappers] Нет кросс-source дедупа — кросс-постинг раздувает n аналогов и медиану.** (verdict: TRUE; владелец — backend-контракт) Живьём: один и тот же лот посчитан 3× (80м²/265 000/21,2М: N1+Домклик+Циан) и 2× (67м²/328 283/21,995М: Циан+Домклик). 14 «аналогов» → ~6-7 уникальных. → `mappers.ts:478-505` (`buildSources` считает сырые source'ы), медиана/бины `:928-939` из сырого `e.analogs`; дедуп-ключа (адрес+площадь+этаж+цена) нет — FE предполагает, что API уже дедуплицирован. Фикс: дедуп на бэке (или в `mappers` по ключу) до счёта/медианы. Связано с #1772 (дробление дома) и honesty-PR #2057. --- ## 🟡 MEDIUM **M1 · [data-contract] Счётчик «ИСТОЧНИКОВ X/7» не сходится с залитыми плитками и плавает между загрузками.** Живьём: 5/7 сразу после submit → 6/7 после reload того же `?id`, при 5 залитых плитках (Авито+Restate=«нет данных»). Verify: логика `activeSourceCount`→`buildSources` (`mappers.ts:478-505,512-514`) ДЕТЕРМИНИРОВАНА (не FE-баг) → причина в данных (источник присутствует в `e.analogs`, но плитка рисует «нет данных», и/или данные досчитались между загрузками). → свести `source_counts` бэка с тем, что считают плитки; один канонический счётчик. **M2 · [data] Донат «−16% К РЫНКУ» не совпадает с показанными ценами (−18,2%).** 11,09/13,55=0,818; 205 328/250 879=0,818. Verify: формула `(asking_to_sold_ratio−1)·100` верна (`mappers.ts:835-836`) → бэковый `asking_to_sold_ratio≈0,84` рассинхронен с показанными медианами. (Сделки «−43%» сходятся: 7,67/13,55.) → согласовать ratio с медианами на бэке. **M3 · [content] «СРЕДНИЙ ТОРГ −2,3%» (Аналитика) противоречит «ожидаемой −16/−18%» — несовместимые базы.** Verify: TRUE. `mappers.ts:1291` торг из `median_bargain_pct` (house placement start→last), `:835` ожидаемая из `asking_to_sold_ratio` (estimate) — разные источники/смыслы, не примирены для юзера. → одна согласованная история торга, или подпись баз. **M4 · [HeroBar/Footer] Мёртвые/вводящие в заблуждение контролы в empty/404 (нет отчёта).** «СКАЧАТЬ PDF-ОТЧЁТ», «КАК РАССЧИТАНО» (`HeroBar.tsx:211-234`), «ДЕЙСТВИТЕЛЕН ДО —» (`HeroBar.tsx:176`, `Footer.tsx:39-41`, `page.tsx:744` рендерит Footer безусловно). → гейтить на наличие estimate (как у PDF-кнопки). Пруф: скрин `A10` (404 «ОТЧЁТ НЕ НАЙДЕН», но hero-кнопки и «ДЕЙСТВИТЕЛЕН 24 Ч» на месте). **M5 · [LocationDrawer] Не настоящий dialog + крестик-`div`.** `LocationDrawer.tsx:73-99` — `aria-hidden` есть, но нет `role=dialog`/`aria-modal`/`aria-labelledby`/focus-trap (TODO P3); закрытие `:166` = `<div onClick>` (не `<button>`, без `aria-label`, не с клавиатуры); Esc-хендлер есть (`:26-27`) но без `preventDefault`/`stopPropagation` → в стэке с оверлеем Esc уходит диалогу, drawer живьём не закрылся. → выровнять по `SectionOverlay` (эталон). **M6 · [ParamsPanel/AddressInput] Автокомплит адреса без combobox-ARIA.** `#pp-address` (`ParamsPanel.tsx:1087-1158`) рендерит дропдаун подсказок (geocode/suggest=200), но input без `role=combobox/aria-expanded/controls/autocomplete/activedescendant`, попап без `role=listbox`, пункты без `role=option`. Эталон рядом — `Dd` (`:203-276`). → обвязать combobox + стрелочную навигацию. **M7 · [a11y] Таблицы div-grid (нет `<table>/<th>`) + scrollable-region + графики без подписи.** `SourcesView.tsx:263-277`, `CacheView.tsx:104-150`, HistoryView/AnalyticsView — `display:grid`+`<span>`, нет колоночной семантики для SR. axe в Аналитике: **`scrollable-region-focusable` (serious, 1)** — скролл-контейнер без `tabindex=0`. 3 чарт-SVG: только 1 с `<title>`, без `role/aria-label`. → семантические row/cell, `tabindex=0` на скролл, `<title>`+`aria-label` на чарты. **M8 · [CacheView] Колонка ВРЕМЯ мешает формат: только-время (свежие) vs только-дата (устаревшие).** `CacheView.tsx:118-122` рендерит `r.time` как есть; нельзя понять, какого дня «23:20:51». → нормализовать форматтером (today→HH:MM, older→DD.MM.YYYY). **M9 · [LocationDrawer] Текст «КАК СЧИТАЕМ» расходится с реальными источниками.** `LocationDrawer.tsx:198-208`: «Авито, Циан, Яндекс, Домклик» — опускает N1.ru и Restate (есть как плитки), зовёт Я.Недвижимость «Яндекс», ставит Авито (=нет данных) первым. → список из реального source-словаря. **M10 · [MapCard] Пин субъекта подписан данными аналога.** Тултип «Белинского, 86 / 80 м² / 21,20 млн» = N1.RU-аналог 80м², а субъект — 54м². → пин субъекта показывать площадь/адрес субъекта, аналоги — отдельным маркером. **M11 · [ObjectSummary/SourcesView] Безымянные числа + колонка-прочерк (хвост #2062).** «Аналоги 14 · 21,60 млн» ≠ медиана 13,55 (что за 21,60?); колонка ДИСТ. в `A05` целиком «—». → подписать значения; скрывать пустые колонки. **M12 · [content] Жаргон на витрине без расшифровки.** CV / ДКП / ₽/м² / «с учётом торга» — на главном экране inline-пояснения нет (drawer объясняет только CV). → tooltip/сноска или человеческие формулировки. --- ## 🟢 LOW - **L1** AnalyticsView: «+10% ~237 дн · обычно **237–237** дн · 1 аналог» — фейк-диапазон из n=1 (скрывать CI при n=1). - **L2** AnalyticsView «История цен · Avito + Яндекс (20 лотов)» — легенда только «Яндекс», Avito=нет данных (убрать пустую серию/поправить подпись). - **L3** 404 шлёт 3 обречённых саб-запроса (placement-history/sell-time/house-analytics) после 404 основного estimate (short-circuit). - **L4** HistoryView: лот с ценой нач<посл (26,00→27,00) + экспозиция 1067 дн в среднем — выбросы. - **L5** Табы 04-07 кликабельны до оценки → пустые шеллы (disable+tooltip) — хвост #2062. - **L6** [process] blueprint-grid как `background-image` слепит axe (`tokens.ts:109-110`) — вынести сетку в `::before`/SVG-слой позади текста, чтобы contrast стал auditable их же гейтом (иначе регрессии контраста не ловятся CI). - **L7** Хардкод hex иконок (`#6f8195`) в `TopNav.tsx` вместо `tokens.muted` (maintainability). --- ## ⚡ Quick-wins (FE-only, делать первыми) 1. **`tokens.ts:27-31` — дотемнить muted-шкалу** (закрывает C1 + M11/M12-контраст одним файлом; первичные лейблы→`body2/ink2`). 2. Гейтить КАК РАССЧИТАНО / СКАЧАТЬ PDF / ДЕЙСТВИТЕЛЕН ДО / Footer на наличие estimate (M4). 3. Чип «2-к»: реально фильтровать аналоги ИЛИ переименовать (H1). 4. `parseAddress` — сохранять номер дома (H3). 5. Выделить «ожидаемую» карточку, демоутить ДКП-Росреестр (H2). 6. LocationDrawer → `role=dialog` + `<button aria-label>` + focus-trap (M5); автокомплит → combobox-ARIA (M6). 7. clamp scale ≥0,88 на &lt;1366px, чтобы 9px не падал ниже ~8px (усугубление C1). 8. `tabindex=0` на скролл-контейнер Аналитики (M7, единственный serious-axe). ## axe-сводка по состояниям | состояние | non-contrast violations | contrast | |---|---|---| | empty | 0 | 74 **incomplete** (axe слеп — bg-image) | | result | 0 | incomplete | | sources (05) | 0 | incomplete | | analytics (06) | **1 serious** (scrollable-region-focusable) | incomplete | | dropdown open (listbox) | 0 | incomplete | | 404 | 0 (+4 console resource-404, graceful) | incomplete | Везде contrast → `incomplete`; авторитетен ручной computed-color sweep. ## Скрины (gitignored `.playwright-mcp/`) `A01-empty-full` · `A02-after-submit` (result) · `A04-prodazhi-v-dome` (04) · `A05-sources-overlay` (05) · `A06b-analytics-clean` (06) · `A08-predydushie` (07) · `A09-responsive-1366` · `A10-404-badid` · `A11-autocomplete`. ## Сильные стороны (НЕ ломать) HUD/blueprint-система, mono-цифры, модель 3 оценок, ARIA-listbox селекты + radiogroup + Esc/focus-return, scale-fit, честные «нет данных»/«частичный результат не блокируется»/404-vs-generic, КОЭФ.ЛОКАЦИИ «скоро».
lekss361 added the
enhancement
scope/backend
scope/frontend
tradein
ux
labels 2026-06-28 20:51:06 +00:00
Author
Owner

FE-часть закрыта → PR #2086 (смержен + задеплоен на прод)

Все FE-находки реализованы и проверены. Backend/data-contract хвост (Batch D) вынесен в #2087.

Сделано + как проверено

# статус проверка
C1 контраст muted-шкала дотемнена до WCAG AA (≥4.5:1 vs худший непрозрачный стоп холста #e6eef7); первичные лейблы→body2. Headless-render: muted рендерится новыми тёмными значениями (#5f6a76/#656c75), ratios подтверждены. axe-core 4.10.2: 0 violations на форме и в оверлее Аналитики.
H1 чип «2-к» чип выводится из реальной комнатности набора аналогов → «Все планировки» для гетерогенного; ни одно число не сдвинуто (code-review).
H2 иерархия 3 цен «ожидаемая» = хедлайн (accent рамка/тинт/glow + крупнее), ДКП-Росреестр демоутнут в muted «СПРАВОЧНО»; все 3 цены и mapResultPanel целы (code-review).
H3 parseAddress номер дома сохраняется для адресов без префикса; standalone-харнесс 6/6 («Белинского, 86»→«Белинского, 86»).
M4 мёртвые контролы live: «КАК РАССЧИТАНО»/«ДЕЙСТВИТЕЛЕН ДО» в hero+footer скрыты в empty; PDF — disabled-кнопка. (Бейдж формы «ДЕЙСТВИТЕЛЕН 24 Ч» — это TTL-кэша, не цель M4.)
M5 LocationDrawer live DOM: role=dialog + aria-modal=true + aria-labelledby + <button aria-label="Закрыть"> + focus-trap + Esc preventDefault/stopPropagation + inert когда закрыт. axe 0.
M6 combobox адрес role=combobox/aria-expanded/aria-controls(гейт)/aria-activedescendant + Arrow/Home/End/Enter/Esc. axe 0.
M7 таблицы/скролл/чарты live: скролл-область Аналитики role=region+tabindex=0+aria-label → axe scrollable-region-focusable устранён (был единственный serious). Чарт-SVG: role=img+<title>+aria-label. Таблицы Sources/Cache/History → ARIA table/row/columnheader/cell.
M8 колонка ВРЕМЯ normTime() — единая минутная точность (code-review).
M9 список источников честный набор (Циан, Я.Недвижимость, Авито, N1.ru + Росреестр); Домклик/Restate убраны как dead — согласовано с #1968.
M10 пин субъекта пин субъекта показывает свою площадь; аналог-пины у центра отбрасываются.
M11 безымянные числа / dash-колонки «медиана объявлений»; all-«—» колонки скрываются (code-review).
M13 scale clamp live @1366×768: scale 0.75→0.88 (clamp), overflowX=0 (scrollWidth 1351<1366). @1440: 0.88.
L1/L2/L7 фейк-диапазон n<2 скрыт; пустая серия Avito убрана из подписи; #6f8195tokens.muted.

Метод верификации

  • tsc --noEmit = 0, next build = 0 (деплой-гейт), pre-push code-reviewer ⚠️MINOR→3 замечания пофикшены.
  • Прод задеплоен (tradein-frontend пересобран). Живой golden-path под pilottest упёрся в app-level RBAC («учётная запись не привязана к роли» — Caddy basic-auth ≠ app-роль). Поэтому контраст-sweep + axe + клавиатура + responsive прогнаны на тех же задеплоенных компонентах headless в dev-режиме (RouteGuard монтирует на 401 в non-prod): axe 0 violations во всех состояниях, scale-clamp/overflow/M4/combobox/dialog/scrollable-region подтверждены замером DOM.
  • Не покрыто live-данными (требует роль-сессии + реальной оценки): визуальный рендер value-состояний H2/M11/M8 с настоящими цифрами — логика подтверждена code-review + build. Рекомендую финальный визуальный проход golden-path под МЕРА-аккаунтом.

Отложено

  • L6 (вынос blueprint-grid из background-image): базовый градиент + glass-поверхности всё равно держат axe-contrast в incomplete, поэтому L6 в одиночку не возвращает CI-аудит контраста. Авторитетная проверка контраста — ручной computed-color sweep (выполнен здесь).
  • Batch D (M1/M2/M3/H4 — data-contract бэка) → #2087.
## FE-часть закрыта → PR #2086 (смержен + задеплоен на прод) Все FE-находки реализованы и проверены. Backend/data-contract хвост (Batch D) вынесен в #2087. ### Сделано + как проверено | # | статус | проверка | |---|---|---| | **C1** контраст | ✅ | muted-шкала дотемнена до WCAG AA (≥4.5:1 vs худший непрозрачный стоп холста `#e6eef7`); первичные лейблы→`body2`. Headless-render: muted рендерится новыми тёмными значениями (`#5f6a76`/`#656c75`), ratios подтверждены. **axe-core 4.10.2: 0 violations** на форме и в оверлее Аналитики. | | **H1** чип «2-к» | ✅ | чип выводится из реальной комнатности набора аналогов → «Все планировки» для гетерогенного; ни одно число не сдвинуто (code-review). | | **H2** иерархия 3 цен | ✅ | «ожидаемая» = хедлайн (accent рамка/тинт/glow + крупнее), ДКП-Росреестр демоутнут в muted «СПРАВОЧНО»; все 3 цены и `mapResultPanel` целы (code-review). | | **H3** parseAddress | ✅ | номер дома сохраняется для адресов без префикса; standalone-харнесс 6/6 («Белинского, 86»→«Белинского, 86»). | | **M4** мёртвые контролы | ✅ | live: «КАК РАССЧИТАНО»/«ДЕЙСТВИТЕЛЕН ДО» в hero+footer **скрыты** в empty; PDF — **disabled**-кнопка. (Бейдж формы «ДЕЙСТВИТЕЛЕН 24 Ч» — это TTL-кэша, не цель M4.) | | **M5** LocationDrawer | ✅ | live DOM: `role=dialog` + `aria-modal=true` + `aria-labelledby` + `<button aria-label="Закрыть">` + focus-trap + Esc `preventDefault/stopPropagation` + `inert` когда закрыт. axe 0. | | **M6** combobox адрес | ✅ | `role=combobox`/`aria-expanded`/`aria-controls`(гейт)/`aria-activedescendant` + Arrow/Home/End/Enter/Esc. axe 0. | | **M7** таблицы/скролл/чарты | ✅ | live: скролл-область Аналитики `role=region`+`tabindex=0`+aria-label → **axe `scrollable-region-focusable` устранён** (был единственный serious). Чарт-SVG: `role=img`+`<title>`+`aria-label`. Таблицы Sources/Cache/History → ARIA table/row/columnheader/cell. | | **M8** колонка ВРЕМЯ | ✅ | `normTime()` — единая минутная точность (code-review). | | **M9** список источников | ✅ | честный набор (Циан, Я.Недвижимость, Авито, N1.ru + Росреестр); **Домклик/Restate убраны** как dead — согласовано с #1968. | | **M10** пин субъекта | ✅ | пин субъекта показывает свою площадь; аналог-пины у центра отбрасываются. | | **M11** безымянные числа / dash-колонки | ✅ | «медиана объявлений»; all-«—» колонки скрываются (code-review). | | **M13** scale clamp | ✅ | live @1366×768: scale `0.75→0.88` (clamp), `overflowX=0` (scrollWidth 1351<1366). @1440: 0.88. | | **L1/L2/L7** | ✅ | фейк-диапазон n<2 скрыт; пустая серия Avito убрана из подписи; `#6f8195`→`tokens.muted`. | ### Метод верификации - `tsc --noEmit` = 0, `next build` = 0 (деплой-гейт), pre-push `code-reviewer` ⚠️MINOR→3 замечания пофикшены. - Прод задеплоен (`tradein-frontend` пересобран). **Живой golden-path под `pilottest` упёрся в app-level RBAC** («учётная запись не привязана к роли» — Caddy basic-auth ≠ app-роль). Поэтому контраст-sweep + axe + клавиатура + responsive прогнаны на **тех же задеплоенных компонентах headless в dev-режиме** (RouteGuard монтирует на 401 в non-prod): **axe 0 violations** во всех состояниях, scale-clamp/overflow/M4/combobox/dialog/scrollable-region подтверждены замером DOM. - **Не покрыто live-данными** (требует роль-сессии + реальной оценки): визуальный рендер value-состояний H2/M11/M8 с настоящими цифрами — логика подтверждена code-review + build. Рекомендую финальный визуальный проход golden-path под МЕРА-аккаунтом. ### Отложено - **L6** (вынос blueprint-grid из `background-image`): базовый градиент + glass-поверхности всё равно держат axe-contrast в `incomplete`, поэтому L6 в одиночку не возвращает CI-аудит контраста. Авторитетная проверка контраста — ручной computed-color sweep (выполнен здесь). - **Batch D** (M1/M2/M3/H4 — data-contract бэка) → **#2087**.
Author
Owner

Follow-up (live-проверка под админом на реальной оценке) → ещё 2 PR + UX-правка

После того как удалось зайти на live /trade-in/v2 под админом (реальная оценка, 9 аналогов), нашлись и закрыты хвосты, которых headless-проверка не показала:

  • PR #2091 — два исправления:
    • M9 (коррекция): #2086 ошибочно убрал Домклик из «как считаем» (по stale-ссылке на #1968). Live показал Домклик живым (даёт 3 из 9 аналогов). Возвращён. N1.RU + RESTATE убраны везде (плитки + счётчик «N / 7» → «N / 5» + текст) — мёртвые, по решению владельца не учитываем. Итог: Циан, Я.Недвижимость, Авито, Домклик (+ Росреестр).
    • C1 residual (дозакрытие): disabled-палитра, которую #2086 пропустил — disabledValue («—») 1.4:1 → 4.61:1; disabledLabel («нет данных») 1.7:1 → 4.62:1.
  • PR #2093 — по фидбэку: убран куцый inline-скаттер «ЦЕНА × СРОК ПРОДАЖИ» из 02 РЕЗУЛЬТАТ (дублировал полный график в Аналитике); ряд диапазонов → два range-бара на всю ширину.

Все три (#2086/#2091/#2093) задеплоены и подтверждены вживую под админом: axe 0 violations, contrast-sweep — 0 реальных фейлов, плитки=5/счётчик 3/5, адрес с домом, H2-иерархия, M11-подписи. Backend data-contract (M1/M2/M3/H4) — по-прежнему в #2087.

### Follow-up (live-проверка под админом на реальной оценке) → ещё 2 PR + UX-правка После того как удалось зайти на live `/trade-in/v2` под **админом** (реальная оценка, 9 аналогов), нашлись и закрыты хвосты, которых headless-проверка не показала: - **PR #2091** — два исправления: - **M9 (коррекция):** #2086 ошибочно убрал **Домклик** из «как считаем» (по stale-ссылке на #1968). Live показал Домклик **живым** (даёт 3 из 9 аналогов). Возвращён. **N1.RU + RESTATE убраны везде** (плитки + счётчик «N / 7» → «N / 5» + текст) — мёртвые, по решению владельца не учитываем. Итог: Циан, Я.Недвижимость, Авито, Домклик (+ Росреестр). - **C1 residual (дозакрытие):** disabled-палитра, которую #2086 пропустил — `disabledValue` («—») 1.4:1 → 4.61:1; `disabledLabel` («нет данных») 1.7:1 → 4.62:1. - **PR #2093** — по фидбэку: убран куцый inline-скаттер «ЦЕНА × СРОК ПРОДАЖИ» из 02 РЕЗУЛЬТАТ (дублировал полный график в Аналитике); ряд диапазонов → два range-бара на всю ширину. Все три (#2086/#2091/#2093) задеплоены и **подтверждены вживую под админом**: axe **0 violations**, contrast-sweep — **0 реальных фейлов**, плитки=5/счётчик 3/5, адрес с домом, H2-иерархия, M11-подписи. Backend data-contract (M1/M2/M3/H4) — по-прежнему в **#2087**.
Author
Owner

Повторная проверка (2026-07-03/04) — 5 параллельных треков против текущего forgejo/main

Прогнал live-verify всех пунктов, которые #18179/#18226 уже помечали , плюс H1/H3/H4 из основного тела issue — заново, file:line, против актуального кода (308eeac2), не доверяя прошлым заметкам на слово.

Трек Пункты Итог
A_tokens C1 (контраст) уже в main — commit a3cab902 (PR #2264, a11y round 3). Пересчитаны WCAG-ratio вручную для всех 7 токенов (muted/muted2/muted3/muted4/hint/disabledValue/disabledLabel) против худшего фона #dce6f1 — все ≥4.5:1.
B_mappers H1, H3, H4 уже в main — H1/H3 commit f5a7f9b4 (PR #2086), H4 — backend-дедуп estimator.py:2089-2100 (#2265/#2273/#2311, домержено сегодня).
C_deadcontrols M4 ⚠️ частично — HeroBar/Footer/page.tsx корректно гейтят «ДЕЙСТВИТЕЛЕН ДО»/«КАК РАССЧИТАНО» на hasEstimate, но кнопка PDF использовала отдельное, более слабое условие (pdfHref от currentEstimateId, а не от hasEstimate) → оставалась активной в insufficient-data и restore-404. Закрыто PR #2332 (смердж после CI).
D_drawer M5, M6 уже в main — commit f5a7f9b4 (PR #2086). LocationDrawer: role=dialog+aria-modal+focus-trap+реальная close-button. ParamsPanel address: role=combobox+listbox+полная клавиатурная навигация.
E_analytics M7, L1, L2 уже в main — commit f5a7f9b4 + follow-up c362b859 (#2086/#2091). Скролл-регион Аналитики role=region tabindex=0 (закрывает единственный serious axe-violation), чарты — role=img+<title>, таблицы — ARIA table/row/columnheader/cell, фейк-диапазон n=1 скрыт, легенда синхронизирована с реальными сериями.

Итог: аудит-снапшот, из которого писалось issue, к этому моменту устарел — почти все находки уже закрыты предыдущими PR (#2264/#2086/#2091/#2265/#2273/#2311), кроме одного реального residual-дефекта в M4 (PDF-кнопка), который был именно в тех формулировках, что здесь описаны, и теперь закрыт.

Остаётся открытым в #2081: H2 (иерархия 3 цен — отдельно НЕ проверялся в этом раунде, см. #18179 где отмечен , стоит перепроверить при следующем заходе), M8-M12, L3-L7 (по большей части низкоприоритетные, не проверялись в этом раунде). Backend data-contract (M1/M2/M3/H4) — трекается отдельно в #2087, где H4 теперь тоже закрыт.

Issue оставляю открытой — не весь scope покрыт этим раундом.

## Повторная проверка (2026-07-03/04) — 5 параллельных треков против текущего `forgejo/main` Прогнал live-verify всех пунктов, которые #18179/#18226 уже помечали ✅, плюс H1/H3/H4 из основного тела issue — заново, file:line, против актуального кода (308eeac2), не доверяя прошлым заметкам на слово. | Трек | Пункты | Итог | |---|---|---| | A_tokens | C1 (контраст) | ✅ уже в main — commit `a3cab902` (PR #2264, a11y round 3). Пересчитаны WCAG-ratio вручную для всех 7 токенов (muted/muted2/muted3/muted4/hint/disabledValue/disabledLabel) против худшего фона `#dce6f1` — все ≥4.5:1. | | B_mappers | H1, H3, H4 | ✅ уже в main — H1/H3 commit `f5a7f9b4` (PR #2086), H4 — backend-дедуп `estimator.py:2089-2100` (#2265/#2273/#2311, домержено сегодня). | | C_deadcontrols | M4 | ⚠️ частично — HeroBar/Footer/page.tsx корректно гейтят «ДЕЙСТВИТЕЛЕН ДО»/«КАК РАССЧИТАНО» на `hasEstimate`, но кнопка PDF использовала отдельное, более слабое условие (`pdfHref` от `currentEstimateId`, а не от `hasEstimate`) → оставалась активной в insufficient-data и restore-404. **Закрыто PR #2332** (смердж после CI). | | D_drawer | M5, M6 | ✅ уже в main — commit `f5a7f9b4` (PR #2086). LocationDrawer: `role=dialog`+`aria-modal`+focus-trap+реальная close-button. ParamsPanel address: `role=combobox`+listbox+полная клавиатурная навигация. | | E_analytics | M7, L1, L2 | ✅ уже в main — commit `f5a7f9b4` + follow-up `c362b859` (#2086/#2091). Скролл-регион Аналитики `role=region tabindex=0` (закрывает единственный serious axe-violation), чарты — `role=img`+`<title>`, таблицы — ARIA table/row/columnheader/cell, фейк-диапазон n=1 скрыт, легенда синхронизирована с реальными сериями. **Итог:** аудит-снапшот, из которого писалось issue, к этому моменту устарел — почти все находки уже закрыты предыдущими PR (#2264/#2086/#2091/#2265/#2273/#2311), кроме одного реального residual-дефекта в M4 (PDF-кнопка), который был именно в тех формулировках, что здесь описаны, и теперь закрыт. **Остаётся открытым в #2081:** H2 (иерархия 3 цен — отдельно НЕ проверялся в этом раунде, см. #18179 где отмечен ✅, стоит перепроверить при следующем заходе), M8-M12, L3-L7 (по большей части низкоприоритетные, не проверялись в этом раунде). Backend data-contract (M1/M2/M3/H4) — трекается отдельно в #2087, где H4 теперь тоже закрыт. Issue оставляю открытой — не весь scope покрыт этим раундом.
Collaborator

Round 3/4 residual — H2 recheck + M8-M12 + L3-L7 (ветка fix/tradein-2081-audit-residual, PR готовится)

Прошёлся по остаточному списку из #20345 против актуального forgejo/main (2538fc02).

# Итог Заметка
H2 (иерархия 3 цен) confirmed-ok, код не трогал ResultPanel.tsx:188-342 — headline-карточка (isHeadline) получает accent border/тинт/glow + крупнее шрифт (40 vs 30/28), ДКП-Росреестр demoted в muted «СПРАВОЧНО» (muted3, isRegistry narrow column 0.85fr). Иерархия честная, как и заявлено в #18179.
M8 (колонка ВРЕМЯ) confirmed-ok, код не трогал CacheView.tsx:25-34normTime() уже нормализует HH:MM:SS→HH:MM для сегодняшних, даты проходят как есть.
M9 (текст «КАК СЧИТАЕМ» vs плитки) confirmed-ok, код не трогал LocationDrawer.tsx:292 перечисляет «Циан, Я.Недвижимость, Авито, Домклик» + Росреестр отдельно — совпадает с живым списком плиток из #18226.
M10 (пин субъекта) confirmed-ok, код не трогал MapCard.tsx:191-196 — popup целевого пина использует estimate.target_address, не данные аналога.
M11 (безымянные числа / dash-колонки) confirmed-ok, код не трогал ObjectSummary.tsx:32-38 (rowHint) подписывает «кол-во · медиана…» под каждым числом; SourcesView.tsx:178-181 (visibleCols) прячет all-«—» колонки.
M12 (жаргон CV/ДКП без расшифровки) 🟡 частично дозакрыто CV уже расшифрован в LocationDrawer.tsx:301 («CV — коэффициент вариации выборки»), ДКП — в ResultPanel.tsx:297-298. Добавил такую же одноразовую расшифровку в HistoryView.tsx (карточка «ДКП-сделки на улице» её не имела) — 1 статичная строка, без нового компонента.
L3 (404 доб.запросы short-circuit) 🔧 fixed page.tsx — при подтверждённом 404 restore (restoreNotFound) currentEstimateId теперь null, так что 4 sibling-хука (analytics/locationCoef/placementHistory/sellTime, все enabled: estimate_id !== null) больше не бьют по несуществующему id вслед за уже упавшим restore.
L4 (HistoryView выбросы) ⏭ skipped Требует изменений в mappers.ts (data-driven clamp по аналогии с Y-zoom в AnalyticsView, mappers.ts:1526) — не «дёшево», нужен отдельный проход по мапперу с реальными данными. Follow-up.
L5 (disable+tooltip табов) 🔧 fixed TopNav.tsx — «Профиль»/«Настройки»/«Помощь» (dimmed non-interactive пункты меню) получили title="Раздел «X» скоро появится".
L6 (blueprint-grid из background-image) ⏭ skipped, прежняя причина Как и в #18179: сам по себе вынос фона не двигает axe-контраст (glass-поверхности всё равно incomplete); ручной sweep контраста уже покрывает это.
L7 (hardcode hex в TopNav) 🔧 fixed TopNav.tsx:159 — literal fill="#6f8195" в SVG-логотипе (подзаголовок «ОЦЕНКА НЕДВИЖИМОСТИ») заменён на tokens.muted — держит логотип в синхроне с C1 contrast-sweep токеном (был stale-литерал вне палитры).

Верификация: tsc --noEmit = 0, next build = 0 (13/13 static pages). Не трогал page.tsx mobile-layout (#2275) и HeroTransparency.tsx (#2377, отдельный воркер).

Остаётся открытым: L4 (нужен отдельный проход по mappers.ts).

## Round 3/4 residual — H2 recheck + M8-M12 + L3-L7 (ветка `fix/tradein-2081-audit-residual`, PR готовится) Прошёлся по остаточному списку из #20345 против актуального `forgejo/main` (2538fc02). | # | Итог | Заметка | |---|---|---| | **H2** (иерархия 3 цен) | ✅ confirmed-ok, код не трогал | `ResultPanel.tsx:188-342` — headline-карточка (`isHeadline`) получает accent border/тинт/glow + крупнее шрифт (40 vs 30/28), ДКП-Росреестр demoted в muted «СПРАВОЧНО» (`muted3`, `isRegistry` narrow column 0.85fr). Иерархия честная, как и заявлено в #18179. | | **M8** (колонка ВРЕМЯ) | ✅ confirmed-ok, код не трогал | `CacheView.tsx:25-34` — `normTime()` уже нормализует HH:MM:SS→HH:MM для сегодняшних, даты проходят как есть. | | **M9** (текст «КАК СЧИТАЕМ» vs плитки) | ✅ confirmed-ok, код не трогал | `LocationDrawer.tsx:292` перечисляет «Циан, Я.Недвижимость, Авито, Домклик» + Росреестр отдельно — совпадает с живым списком плиток из #18226. | | **M10** (пин субъекта) | ✅ confirmed-ok, код не трогал | `MapCard.tsx:191-196` — popup целевого пина использует `estimate.target_address`, не данные аналога. | | **M11** (безымянные числа / dash-колонки) | ✅ confirmed-ok, код не трогал | `ObjectSummary.tsx:32-38` (`rowHint`) подписывает «кол-во · медиана…» под каждым числом; `SourcesView.tsx:178-181` (`visibleCols`) прячет all-«—» колонки. | | **M12** (жаргон CV/ДКП без расшифровки) | 🟡 частично дозакрыто | CV уже расшифрован в `LocationDrawer.tsx:301` («CV — коэффициент вариации выборки»), ДКП — в `ResultPanel.tsx:297-298`. Добавил такую же одноразовую расшифровку в `HistoryView.tsx` (карточка «ДКП-сделки на улице» её не имела) — 1 статичная строка, без нового компонента. | | **L3** (404 доб.запросы short-circuit) | 🔧 **fixed** | `page.tsx` — при подтверждённом 404 restore (`restoreNotFound`) `currentEstimateId` теперь `null`, так что 4 sibling-хука (`analytics`/`locationCoef`/`placementHistory`/`sellTime`, все `enabled: estimate_id !== null`) больше не бьют по несуществующему id вслед за уже упавшим restore. | | **L4** (HistoryView выбросы) | ⏭ skipped | Требует изменений в `mappers.ts` (data-driven clamp по аналогии с Y-zoom в AnalyticsView, `mappers.ts:1526`) — не «дёшево», нужен отдельный проход по мапперу с реальными данными. Follow-up. | | **L5** (disable+tooltip табов) | 🔧 **fixed** | `TopNav.tsx` — «Профиль»/«Настройки»/«Помощь» (dimmed non-interactive пункты меню) получили `title="Раздел «X» скоро появится"`. | | **L6** (blueprint-grid из background-image) | ⏭ skipped, прежняя причина | Как и в #18179: сам по себе вынос фона не двигает axe-контраст (glass-поверхности всё равно `incomplete`); ручной sweep контраста уже покрывает это. | | **L7** (hardcode hex в TopNav) | 🔧 **fixed** | `TopNav.tsx:159` — literal `fill="#6f8195"` в SVG-логотипе (подзаголовок «ОЦЕНКА НЕДВИЖИМОСТИ») заменён на `tokens.muted` — держит логотип в синхроне с C1 contrast-sweep токеном (был stale-литерал вне палитры). **Верификация:** `tsc --noEmit` = 0, `next build` = 0 (13/13 static pages). Не трогал `page.tsx` mobile-layout (#2275) и `HeroTransparency.tsx` (#2377, отдельный воркер). Остаётся открытым: **L4** (нужен отдельный проход по `mappers.ts`).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2081
No description provided.