Skip to content

[BUG] [alpha] Systemic Failure: Stacked Modals cause "Scroll Leakage" to underlying UI #71

@pawar08

Description

@pawar08

[BUG] [alpha] Systemic Failure: Stacked Modals cause "Scroll Leakage" to underlying UI

Description

A systemic regression exists in the CortexModal component where closing a nested (top-level) modal prematurely restores the body scroll of the underlying IDE, even if a parent modal is still active. This creates "Scroll Leakage" where the user can scroll the code editor or other UI elements behind a blocking dialog.

Impact

  • Context Loss: Users can accidentally scroll away from their current focus while a modal is still open.
  • UX Instability: Breaking the "blocking" promise of modals leads to a confusing and unpolished experience.
  • Systemic: Affects every combination of stacked modals in the IDE (e.g., Search Replace + File Delete, extension installs + confirmations).

Reproduction Steps

  1. Trigger Modal A: Open the Search Sidebar, perform any search, and click the Replace All button. This opens a "Replace All" confirmation modal. (Verify background is NOT scrollable).
  2. Trigger Modal B: While the "Replace All" modal is open, go to the Explorer and right-click any file, then select Delete. This opens a second modal (Delete confirmation) on top.
  3. The Leak: Click Cancel on the top (Delete) modal to close it.
  4. Observer Bug: Notice that the main editor/UI background is now SCROLLABLE even though the "Replace All" modal is still open and blocking the view.

Root Cause Analysis

The bug is located in CortexModal.tsx.

The restoreDocumentState function (line 94) blindly resets the body overflow to empty:

94:   const restoreDocumentState = () => {
95:     if (typeof document !== "undefined") {
96:       document.body.style.overflow = ""; // <--- ERROR: Resets overflow regardless of other modals
97:     }
98:     previousActiveElement?.focus();
99:   };

This function is triggered in the createEffect block whenever open becomes false (line 179):

175:     } else {
176:       setIsVisible(false);
177:       closeAnimationTimeout = setTimeout(() => {
178:         setIsAnimating(false);
179:         restoreDocumentState(); // <--- ERROR: Triggered on any modal close
180:         closeAnimationTimeout = undefined;
181:       }, 200);

Suggested Fix

Implement a global modal counter or use the existing useModalActiveOptional context to track the number of active modals. restoreDocumentState should only be called if the active modal count is zero.


Verified on Linux (Cortex IDE Alpha)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions