feat(tradein): «Росреестр» button — copy address + open EGRN request form #550

Merged
lekss361 merged 1 commit from feat/tradein-rosreestr-deeplink into main 2026-05-24 19:42:10 +00:00
Owner

What

В строках analogs (ListingsCard) и deals (DealsCard) добавлена маленькая ghost-button «Росреестр ↗». Клик:

  1. Копирует address в clipboard через navigator.clipboard.writeText()
  2. Открывает форму на rosreestr.gov.ru/eservices/request_info_from_egrn/ в новой вкладке (noopener,noreferrer)

Дальше пользователь сам вставляет адрес в форму и заказывает выписку о переходе прав (~850 ₽) если нужна углублённая проверка конкретной квартиры.

Why

Open dataset Росреестра не отдаёт кадастровые номера — поэтому полностью автоматическая deeplink-навигация невозможна. Минимально полезный UX: одним кликом убрать ручное копирование адреса из карточки.

Changes

  • tradein-mvp/frontend/src/lib/rosreestr.ts — NEW, ROSREESTR_REQUEST_URL + openRosreestrWithAddress(address) (graceful если clipboard недоступен)
  • tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx — кнопка в DealRow, новая колонка в thead
  • tradein-mvp/frontend/src/components/trade-in/ListingsCard.tsx — кнопка в AnalogRow рядом с external-link иконкой
  • tradein-mvp/frontend/src/components/trade-in/trade-in.css.rosreestr-btn ghost-style через CSS-переменные (--border, --muted, --fg)

Constraints соблюдены

  • Pure frontend, никаких backend / schema / API изменений
  • AnalogLot type не трогали
  • RecentSoldList не затронут (RecentSoldEntry не имеет address)

Verification

  • TypeScript typecheck — pass
  • pre-commit hooks (trim ws, prettier, ruff на co-changed paths) — pass

Refs: research session 2026-05-24 — PR-D из 4-PR roadmap (после PR-A в #549).

## What В строках analogs (ListingsCard) и deals (DealsCard) добавлена маленькая ghost-button «Росреестр ↗». Клик: 1. Копирует `address` в clipboard через `navigator.clipboard.writeText()` 2. Открывает форму на rosreestr.gov.ru/eservices/request_info_from_egrn/ в новой вкладке (`noopener,noreferrer`) Дальше пользователь сам вставляет адрес в форму и заказывает выписку о переходе прав (~850 ₽) если нужна углублённая проверка конкретной квартиры. ## Why Open dataset Росреестра не отдаёт кадастровые номера — поэтому полностью автоматическая deeplink-навигация невозможна. Минимально полезный UX: одним кликом убрать ручное копирование адреса из карточки. ## Changes - `tradein-mvp/frontend/src/lib/rosreestr.ts` — NEW, `ROSREESTR_REQUEST_URL` + `openRosreestrWithAddress(address)` (graceful если clipboard недоступен) - `tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx` — кнопка в `DealRow`, новая колонка в thead - `tradein-mvp/frontend/src/components/trade-in/ListingsCard.tsx` — кнопка в `AnalogRow` рядом с external-link иконкой - `tradein-mvp/frontend/src/components/trade-in/trade-in.css` — `.rosreestr-btn` ghost-style через CSS-переменные (`--border`, `--muted`, `--fg`) ## Constraints соблюдены - Pure frontend, никаких backend / schema / API изменений - `AnalogLot` type не трогали - `RecentSoldList` не затронут (`RecentSoldEntry` не имеет `address`) ## Verification - TypeScript typecheck — pass - pre-commit hooks (trim ws, prettier, ruff на co-changed paths) — pass Refs: research session 2026-05-24 — PR-D из 4-PR roadmap (после PR-A в #549).
lekss361 added 1 commit 2026-05-24 19:37:42 +00:00
В строках analogs/deals добавлена кнопка «Росреестр ↗»: клик копирует адрес
в clipboard и открывает форму на rosreestr.gov.ru, где пользователь может
заказать выписку о переходе прав (~850 ₽) для углублённой проверки.

Open dataset Росреестра не отдаёт кадастровые номера — поэтому работаем
с адресом через clipboard. Pure frontend, никаких backend изменений.

Refs: research session 2026-05-24 — PR-D из 4-PR roadmap.
Author
Owner

Deep Code Review — verdict APPROVE

Summary

  • Status: APPROVE
  • Files: 4 (frontend only — 1 new util, 2 components, 1 css)
  • Lines: +50 / -1
  • Scope: P2 frontend feature, no backend/schema/API touch

Security — clean

  • ROSREESTR_REQUEST_URL is a hardcoded constant string — address is never interpolated into the URL → no XSS, no URL-encoding need
  • window.open(URL, "_blank", "noopener,noreferrer") correctly defends against tabnabbing
  • safeUrl (PR #512) intentionally not used here: it's for dynamic URLs from scraped data; this URL is a trusted literal
  • address from AnalogLot.address is typed non-nullable string, safe for clipboard.writeText

Correctness — clean

  • DealsCard: <th /> added to thead to match new <td> button column (6=6)
  • ListingsCard: already had <th /> for link cell, button placed inside existing cell with whiteSpace: nowrap — sensible
  • void prefix on async onClick handler is the correct TS fire-and-forget pattern
  • Clipboard try/catch is intentional graceful degradation, documented in PR body

Conventions — clean

  • CSS tokens --border, --muted, --fg all defined in trade-in.css root
  • No hardcoded colors
  • "use client" not needed (already in parent component)
  • Imports use @/lib/... alias consistent with safeUrl import pattern

Minor / optional polish (NOT blocking)

  1. Popup-blocker theoretical risk: await navigator.clipboard.writeText(...) before window.open — Safari/Firefox can lose user-gesture context after await. Clipboard writes typically resolve in <1ms so in practice safe; if regressions surface, swap order (open first, write after) or drop the await.
  2. No user feedback on clipboard fail/success: silent catch — user has no toast/aria-live signal. Minor UX gap; punt to a future PR.
  3. Accessibility: button has no aria-label but visible text "Росреестр ↗" is the accessible name; title provides tooltip. Conforms to project convention (icon-only <a> row-link uses aria-label, text buttons don't).
  4. No test added: tradein-mvp/frontend has no Jest/Vitest infra — project-wide gap, not regression. Acceptable.

Cross-file impact

  • No type changes, no API contract change, no shared util touched
  • Component blast radius limited to DealsCard + ListingsCard rows

Merging via squash.

<!-- gendesign-review-bot: sha=4d09b3b verdict=approve --> ## Deep Code Review — verdict APPROVE ### Summary - Status: APPROVE - Files: 4 (frontend only — 1 new util, 2 components, 1 css) - Lines: +50 / -1 - Scope: P2 frontend feature, no backend/schema/API touch ### Security — clean - `ROSREESTR_REQUEST_URL` is a hardcoded constant string — `address` is never interpolated into the URL → no XSS, no URL-encoding need - `window.open(URL, "_blank", "noopener,noreferrer")` correctly defends against tabnabbing - `safeUrl` (PR #512) intentionally not used here: it's for *dynamic* URLs from scraped data; this URL is a trusted literal - `address` from `AnalogLot.address` is typed non-nullable `string`, safe for `clipboard.writeText` ### Correctness — clean - DealsCard: `<th />` added to thead to match new `<td>` button column (6=6) - ListingsCard: already had `<th />` for link cell, button placed inside existing cell with `whiteSpace: nowrap` — sensible - `void` prefix on async `onClick` handler is the correct TS fire-and-forget pattern - Clipboard `try/catch` is intentional graceful degradation, documented in PR body ### Conventions — clean - CSS tokens `--border`, `--muted`, `--fg` all defined in trade-in.css root - No hardcoded colors - `"use client"` not needed (already in parent component) - Imports use `@/lib/...` alias consistent with safeUrl import pattern ### Minor / optional polish (NOT blocking) 1. **Popup-blocker theoretical risk**: `await navigator.clipboard.writeText(...)` before `window.open` — Safari/Firefox can lose user-gesture context after `await`. Clipboard writes typically resolve in <1ms so in practice safe; if regressions surface, swap order (open first, write after) or drop the `await`. 2. **No user feedback on clipboard fail/success**: silent `catch` — user has no toast/aria-live signal. Minor UX gap; punt to a future PR. 3. **Accessibility**: button has no `aria-label` but visible text "Росреестр ↗" *is* the accessible name; `title` provides tooltip. Conforms to project convention (icon-only `<a>` row-link uses `aria-label`, text buttons don't). 4. **No test added**: tradein-mvp/frontend has no Jest/Vitest infra — project-wide gap, not regression. Acceptable. ### Cross-file impact - No type changes, no API contract change, no shared util touched - Component blast radius limited to DealsCard + ListingsCard rows Merging via squash.
lekss361 merged commit 5bfacd51b0 into main 2026-05-24 19:42:10 +00:00
lekss361 deleted branch feat/tradein-rosreestr-deeplink 2026-05-24 19:42:10 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#550
No description provided.