From c5cafd384785fbb8d75dbd303840148f5db8ddd6 Mon Sep 17 00:00:00 2001 From: maxmilian <3001335+maxmilian@users.noreply.github.com> Date: Mon, 18 May 2026 20:02:44 -0700 Subject: [PATCH] fix(web): portal Change Model modal so it renders above the app sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard's main column is `relative z-2` (App.tsx), which creates a stacking context that traps fixed descendants below the app sidebar (`z-50`). `ModelPickerDialog` renders `fixed inset-0 z-[100]` inline, so its z-100 is scoped to z-2 and the sidebar covers its left edge. The bug is visible across all themes but only obvious in the Large theme variants (Hermes Teal (Large), etc.) where the larger root font widens the dialog into the sidebar's column. Toast.tsx already documents the same trap and uses the same `createPortal(..., document.body)` escape. This commit ports the picker; the same pattern affects other inline z-[100] modals in the dashboard (OAuthLoginModal, Cron / Models / Profiles page modals) and is left for a follow-up — keeping this PR scoped to the reporter's specific case. Fixes #28103 Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/components/ModelPickerDialog.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/web/src/components/ModelPickerDialog.tsx b/web/src/components/ModelPickerDialog.tsx index d99ea09a8ab..22b2cb1bce8 100644 --- a/web/src/components/ModelPickerDialog.tsx +++ b/web/src/components/ModelPickerDialog.tsx @@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input"; import type { GatewayClient } from "@/lib/gatewayClient"; import { Check, Search, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; +import { createPortal } from "react-dom"; /** * Two-stage model picker modal. @@ -194,7 +195,14 @@ export function ModelPickerDialog(props: Props) { } }; - return ( + // Portal to document.body: the main dashboard column in App.tsx is + // `relative z-2`, which creates a stacking context that traps fixed + // descendants below the app sidebar (z-50). Without the portal this + // modal's z-[100] is scoped to z-2 and the sidebar covers its left + // edge — visible especially in the Large theme variants where the + // larger root font widens the dialog into the sidebar's column. See + // Toast.tsx for the same pattern. + return createPortal(
e.target === e.currentTarget && onClose()} @@ -296,7 +304,8 @@ export function ModelPickerDialog(props: Props) {
- + , + document.body, ); }