Skip to content

fix: dismiss session modals when clicking the side margins#4374

Open
brantsrasmus wants to merge 2 commits into
umami-software:devfrom
brantsrasmus:fix/session-modal-dismiss-outside-click
Open

fix: dismiss session modals when clicking the side margins#4374
brantsrasmus wants to merge 2 commits into
umami-software:devfrom
brantsrasmus:fix/session-modal-dismiss-outside-click

Conversation

@brantsrasmus

@brantsrasmus brantsrasmus commented Jul 3, 2026

Copy link
Copy Markdown

Problem

The session detail modal (SessionModal) and session profile modal (SessionProfileModal) can only be dismissed by clicking the thin area above the sheet — clicking the dark space to the left or right of the sheet does nothing.

Cause

Both modals apply their 1320px max width to an inner <Column> rather than to the <Modal> itself:

<Modal placement="bottom" offset="80px" isDismissable>
  <Column height="100%" maxWidth="1320px" style={{ margin: '0 auto' }}>
    ...

react-zen's Modal with placement="bottom" renders the react-aria modal box as absolute inset-x-0 bottom-0 h-[calc(100dvh-offset)] — i.e. full viewport width. react-aria only dismisses on interaction outside that box, but the box's transparent side margins sit on top of the underlay and swallow the click. Only the offset strip above the sheet is real underlay, so only clicks there dismiss.

ReplayModal (same placement="bottom") is unaffected because it correctly sets the width on the Modal box itself (via a CSS module), so its box is the centered ~1320px sheet.

Fix

Move the width from the inner Column onto the Modal box via a CSS module, matching the existing ReplayModal pattern. The click-catcher box is now the centered sheet, so the side margins become real overlay and dismiss on click.

Testing

Adds an e2e regression test (tests/e2e/session-modal-dismiss.spec.ts) that opens the modal on a wide viewport and asserts left- and right-margin clicks dismiss it, with an in-sheet control click that must keep it open. Verified the test fails on dev without this change and passes with it.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

SessionModal and SessionProfileModal set their 1320px max width on an
inner Column rather than on the Modal itself. The react-zen Modal box
(placement="bottom") therefore stretched to the full viewport width
(inset-x-0), so its transparent left/right margins sat on top of the
underlay and swallowed outside-clicks. Only the thin offset strip above
the sheet was dismissable.

Move the width onto the Modal box via a CSS module — matching the
existing pattern in ReplayModal — so the click-catcher is the centered
~1320px sheet and the side margins become real overlay again.

Adds an e2e regression test covering left/right margin dismissal with an
in-sheet control click.
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

@brantsrasmus is attempting to deploy a commit to the Umami Software Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a click-dismiss bug in SessionModal and SessionProfileModal where the react-aria modal box spanned the full viewport width, causing transparent side margins to swallow clicks instead of dismissing. The fix moves the 1320px width constraint from an inner Column onto the Modal box itself via a new CSS module, matching the existing ReplayModal pattern.

  • SessionModal.module.css (new): defines width: min(calc(100dvw - 48px), 1320px) applied to the <Modal> element in both session modals.
  • SessionModal.tsx / SessionProfileModal.tsx: remove maxWidth / margin: 0 auto from the inner <Column> and apply the CSS module class directly on <Modal>.
  • tests/e2e/session-modal-dismiss.spec.ts (new): adds a 1600px-viewport e2e regression test verifying left-margin, right-margin, and in-sheet clicks behave correctly.

Confidence Score: 4/5

Safe to merge — the fix is a narrow, well-understood CSS scoping change with a matching e2e regression test.

The core fix is correct and follows the existing ReplayModal pattern, but SessionModal does not destructure and merge its className prop before spreading {...props}, meaning a caller that passes className would silently clobber styles.modal and revert the fix. No current caller does this, so there is no immediate breakage, but the inconsistency with ReplayModal is worth tidying before merge.

src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx — className prop merging should follow the ReplayModal pattern.

Important Files Changed

Filename Overview
src/app/(main)/websites/[websiteId]/sessions/SessionModal.module.css New CSS module defining width: min(calc(100dvw - 48px), 1320px) on the modal box — matches the ReplayModal pattern exactly, though overflow: hidden present in ReplayModal.module.css is omitted here.
src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx Applies styles.modal directly on the <Modal> element and removes maxWidth from the inner Column — correct fix, but unlike ReplayModal.tsx, className from spread {...props} can silently override styles.modal since it isn't merged first.
src/app/(main)/websites/[websiteId]/sessions/SessionProfileModal.tsx Same fix as SessionModal: imports and applies the shared CSS module class on the Modal element, removes maxWidth from the Column. No className prop passthrough so the spread-order concern doesn't apply here.
tests/e2e/session-modal-dismiss.spec.ts New e2e regression test for the dismiss fix; viewport is set to 1600px so the side margins exist. One test calls page.viewportSize().width without a null check — safe in practice given test.use, but TypeScript allows null there.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Underlay as react-aria Underlay
    participant ModalBox as Modal Box (click-catcher)
    participant Dialog

    Note over User,Dialog: Before fix — modal box spans full viewport width
    User->>ModalBox: clicks side margin (transparent area)
    ModalBox-->>User: swallows click (no dismiss)

    Note over User,Dialog: After fix — modal box is centered at max 1320px
    User->>Underlay: clicks side margin (real underlay)
    Underlay->>ModalBox: onDismiss triggered
    ModalBox->>Dialog: closes modal
    Dialog-->>User: modal dismissed, URL param cleared
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant Underlay as react-aria Underlay
    participant ModalBox as Modal Box (click-catcher)
    participant Dialog

    Note over User,Dialog: Before fix — modal box spans full viewport width
    User->>ModalBox: clicks side margin (transparent area)
    ModalBox-->>User: swallows click (no dismiss)

    Note over User,Dialog: After fix — modal box is centered at max 1320px
    User->>Underlay: clicks side margin (real underlay)
    Underlay->>ModalBox: onDismiss triggered
    ModalBox->>Dialog: closes modal
    Dialog-->>User: modal dismissed, URL param cleared
Loading

Comments Outside Diff (1)

  1. src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx, line 11 (link)

    P2 Unlike ReplayModal, which destructures and merges className before spreading {...props}, SessionModal places className={styles.modal} before the spread. Since {...props} comes last, any className passed by a caller will silently override styles.modal, reverting the fix without any warning. Aligning with the ReplayModal pattern avoids this.

Reviews (1): Last reviewed commit: "fix: dismiss session modals when clickin..." | Re-trigger Greptile

Comment thread src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx
Comment thread tests/e2e/session-modal-dismiss.spec.ts Outdated
- SessionModal: merge caller-provided className with styles.modal instead
  of letting it be clobbered by the {...props} spread (matches ReplayModal).
- e2e test: narrow boundingBox()/viewport nullability without non-null
  assertions (keeps biome's noNonNullAssertion happy) via a getSheetBox
  helper and a shared VIEWPORT constant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant