fix(tradein): MapPicker через portal в document.body — z-index конфликт с SourcesProgress (#771) #782

Merged
bot-reviewer merged 1 commit from feat/771-mappicker-portal into main 2026-05-30 16:37:04 +00:00
Collaborator

Root cause

MapPicker (position:fixed; z-index:1000 + backdrop) монтируется внутри subtree формы (EstimateForm). В дереве есть position:sticky; z-index:50 предок (trade-in.css:73-75), который создаёт stacking-contextz-index:1000 оверлея заперт внутри z-50 слоя и не может надёжно перекрыть SourcesProgress, который рендерится в соседнем .result-col (page.tsx:221). Отсюда полоски агрегации наезжали поверх карты.

Fix

Рендерим оверлей через createPortal(…, document.body) — DOM-узел переносится в корень <body>, escape всех ancestor stacking-context'ов. z-index:1000 + полупрозрачный backdrop теперь гарантированно над loading-UI. Добавлен SSR-safe mounted-guard (document доступен только после монтирования). SourcesProgress data-wiring не тронут.

Test plan

  • npx tsc --noEmit → 0 ошибок
  • npm run build → ✓ Compiled successfully, 10/10 pages
  • Live smoke (qa): форма → начать оценку (агрегация идёт) → открыть карту → полоски НЕ наезжают; desktop И mobile ≤390px; карта кликабельна, onPick/onClose работают; полоски корректны когда карта закрыта.

Refs #771

## Root cause `MapPicker` (`position:fixed; z-index:1000` + backdrop) монтируется **внутри** subtree формы (`EstimateForm`). В дереве есть `position:sticky; z-index:50` предок (trade-in.css:73-75), который создаёт **stacking-context** → `z-index:1000` оверлея заперт внутри z-50 слоя и не может надёжно перекрыть `SourcesProgress`, который рендерится в соседнем `.result-col` (`page.tsx:221`). Отсюда полоски агрегации наезжали поверх карты. ## Fix Рендерим оверлей через `createPortal(…, document.body)` — DOM-узел переносится в корень `<body>`, escape **всех** ancestor stacking-context'ов. `z-index:1000` + полупрозрачный backdrop теперь гарантированно над loading-UI. Добавлен SSR-safe mounted-guard (`document` доступен только после монтирования). SourcesProgress data-wiring не тронут. ## Test plan - [x] `npx tsc --noEmit` → 0 ошибок - [x] `npm run build` → ✓ Compiled successfully, 10/10 pages - [ ] **Live smoke (qa)**: форма → начать оценку (агрегация идёт) → открыть карту → полоски НЕ наезжают; desktop И mobile ≤390px; карта кликабельна, onPick/onClose работают; полоски корректны когда карта закрыта. Refs #771
bot-frontend added 1 commit 2026-05-30 16:34:43 +00:00
MapPicker (position:fixed; z-index:1000) монтировался внутри subtree формы,
где sticky-предок (z-index:50) создаёт stacking-context → оверлей был заперт
и полоски SourcesProgress наезжали сверху. Рендерим оверлей через createPortal
в document.body — escape всех ancestor stacking-context'ов. SSR-mounted guard.

Refs #771
bot-reviewer approved these changes 2026-05-30 16:36:53 +00:00
bot-reviewer left a comment
Collaborator

APPROVE

MapPicker portal-фикс (#771).

Correctness — корень: классический stacking-context trap — position:fixed; z-index:1000 оверлей внутри position:sticky; z-index:50 предка заперт в z-50 слое и не может перекрыть SourcesProgress в соседней колонке. Фикс createPortal(overlay, document.body) экранирует все ancestor stacking-context'ы — идиоматичное, корректное React-решение. SSR-safe mounted-guard (useEffectsetMounted(true), if (!mounted) return null) предотвращает доступ к document на server-render (иначе createPortal крашит SSR). Backdrop/onClose/onPick сохранены (DOM перенесён, логика не тронута). SourcesProgress data-wiring не тронут по scope. tsc 0, build ✓ 10/10.

Scope/security — только MapPicker.tsx; нет secrets, нет self-extending триггера.

Verdict: APPROVE (#771). Live z-index визуал (desktop + ≤390px) → qa.

<!-- gendesign-review-bot: sha=52db40c verdict=approve --> ✅ APPROVE MapPicker portal-фикс (#771). **Correctness** — корень: классический stacking-context trap — `position:fixed; z-index:1000` оверлей внутри `position:sticky; z-index:50` предка заперт в z-50 слое и не может перекрыть `SourcesProgress` в соседней колонке. Фикс `createPortal(overlay, document.body)` экранирует все ancestor stacking-context'ы — идиоматичное, корректное React-решение. SSR-safe `mounted`-guard (`useEffect`→`setMounted(true)`, `if (!mounted) return null`) предотвращает доступ к `document` на server-render (иначе createPortal крашит SSR). Backdrop/`onClose`/`onPick` сохранены (DOM перенесён, логика не тронута). SourcesProgress data-wiring не тронут по scope. tsc 0, build ✓ 10/10. **Scope/security** — только MapPicker.tsx; нет secrets, нет self-extending триггера. **Verdict: ✅ APPROVE** (#771). Live z-index визуал (desktop + ≤390px) → qa.
bot-reviewer merged commit fdbd6ed70d into main 2026-05-30 16:37:04 +00:00
bot-reviewer deleted branch feat/771-mappicker-portal 2026-05-30 16:37:04 +00:00
Sign in to join this conversation.
No reviewers
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#782
No description provided.