diff --git a/frontend/editor/src/core/components/WindowTitleBar.tsx b/frontend/editor/src/core/components/WindowTitleBar.tsx deleted file mode 100644 index bfedf785ea..0000000000 --- a/frontend/editor/src/core/components/WindowTitleBar.tsx +++ /dev/null @@ -1,6 +0,0 @@ -// Core stub: the browser build has no custom window chrome. The desktop layer -// shadows this (desktop/components/WindowTitleBar.tsx) with a real title bar on -// Windows. Rendered by AppFrame, so it must resolve in every build. -export function WindowTitleBar() { - return null; -} diff --git a/frontend/editor/src/core/components/filesPage/FilesPage.css b/frontend/editor/src/core/components/filesPage/FilesPage.css index c4ab11f6be..6d4070edb6 100644 --- a/frontend/editor/src/core/components/filesPage/FilesPage.css +++ b/frontend/editor/src/core/components/filesPage/FilesPage.css @@ -21,9 +21,7 @@ align-items: center; gap: 1rem; min-height: 48px; - /* Right padding also clears the desktop window-controls overlay so the whole - grid (search + actions) reflows inside it (0 unless Windows custom chrome). */ - padding: 0 calc(0.75rem + var(--wincontrols-w, 0px)) 0 0.75rem; + padding: 0 0.75rem; border-bottom: 1px solid var(--c-border-subtle); background: var(--c-bg-raised); flex-shrink: 0; diff --git a/frontend/editor/src/core/components/layout/AppFrame.tsx b/frontend/editor/src/core/components/layout/AppFrame.tsx index 8b79d181e9..38fa92ecba 100644 --- a/frontend/editor/src/core/components/layout/AppFrame.tsx +++ b/frontend/editor/src/core/components/layout/AppFrame.tsx @@ -3,7 +3,6 @@ import { Outlet } from "react-router-dom"; import { LoadingFallback } from "@app/components/shared/LoadingFallback"; import { QuickNavHostProvider } from "@app/contexts/QuickNavHostContext"; import { QuickNavRailHost } from "@app/components/shared/quickNav/QuickNavRailHost"; -import { WindowTitleBar } from "@app/components/WindowTitleBar"; import "@app/components/layout/AppFrame.css"; /** The rail renders once outside both apps; Suspense sits inside it, not above. */ @@ -11,10 +10,6 @@ export function AppFrame() { return (
- {/* Desktop (Windows) window controls, drawn as a fixed overlay in the - top-right corner so the rail and panels run to the window edge; null - on web + macOS/Linux. */} -
}> diff --git a/frontend/editor/src/core/components/tools/ToolPanel.css b/frontend/editor/src/core/components/tools/ToolPanel.css index 64624ef0cc..b27885ccf5 100644 --- a/frontend/editor/src/core/components/tools/ToolPanel.css +++ b/frontend/editor/src/core/components/tools/ToolPanel.css @@ -31,11 +31,7 @@ display: flex; flex-direction: column; align-items: stretch; - /* Extra top space keeps the expand toggle clear of the desktop window - controls overlay. Derive the ~control-height from --wincontrols-w (which is - 138px when the custom Windows chrome is active, 0 otherwise) via min(), so - it needs only that one variable. */ - padding-top: calc(10px + min(var(--wincontrols-w, 0px), 2rem)); + padding-top: 10px; height: 100%; width: 100%; min-height: 0; @@ -161,19 +157,9 @@ gap: 0.5rem; min-height: 52px; padding: 0.5rem 0.75rem; - /* Keep the collapse toggle clear of the desktop window-controls overlay - (--wincontrols-w is 0 unless the custom Windows title bar is active). */ - padding-right: calc(0.75rem + var(--wincontrols-w, 0px)); box-sizing: border-box; } -/* Active-tool header (e.g. Automate) sits at the panel's top-right too; keep its - close button clear of the window-controls overlay. Scoped to the tool panel so - the shared header elsewhere (AI chat, etc.) is untouched. */ -.tool-panel .sui-panelhdr__close { - margin-right: var(--wincontrols-w, 0px); -} - .tool-panel__compact-title { flex: 1 1 auto; min-width: 0; diff --git a/frontend/editor/src/desktop/components/AppProviders.tsx b/frontend/editor/src/desktop/components/AppProviders.tsx index d7405ef83d..0285c107ae 100644 --- a/frontend/editor/src/desktop/components/AppProviders.tsx +++ b/frontend/editor/src/desktop/components/AppProviders.tsx @@ -1,6 +1,7 @@ import { ReactNode, useEffect, useRef, useState } from "react"; import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders"; import { DesktopConfigSync } from "@app/components/DesktopConfigSync"; +import { WindowTitleBar } from "@app/components/WindowTitleBar"; import { DesktopQueryCacheReset } from "@app/components/DesktopQueryCacheReset"; import { DesktopBannerInitializer } from "@app/components/DesktopBannerInitializer"; import { SaveShortcutListener } from "@app/components/SaveShortcutListener"; @@ -328,6 +329,7 @@ export function AppProviders({ children }: { children: ReactNode }) { > {/* Also here: the auth check below switches mode pre-authChecked. */} +
{updatePopupModal} @@ -354,6 +356,7 @@ export function AppProviders({ children }: { children: ReactNode }) { }} > + diff --git a/frontend/editor/src/desktop/components/WindowTitleBar.tsx b/frontend/editor/src/desktop/components/WindowTitleBar.tsx index 7a7d506e44..a11cd72875 100644 --- a/frontend/editor/src/desktop/components/WindowTitleBar.tsx +++ b/frontend/editor/src/desktop/components/WindowTitleBar.tsx @@ -8,6 +8,9 @@ import FilterNoneIcon from "@mui/icons-material/FilterNone"; import CloseIcon from "@mui/icons-material/Close"; import { getDesktopOs, DesktopOs } from "@app/services/platformService"; import styles from "@app/components/WindowTitleBar.module.css"; +// Desktop-only skin that reserves the controls' corner across core layout +// surfaces; every rule is gated by the data-window-controls flag set below. +import "@app/components/windowChrome.css"; // Seed from the UA so the bar (and its reserved height) is present on the first // frame on Windows, avoiding a layout shift. getDesktopOs() confirms it right @@ -19,10 +22,11 @@ const seedIsWindows = * Custom window controls for the Windows desktop build. The native caption is * removed in Rust (decorations:false), so this draws minimize/maximize/close as * a fixed overlay pinned to the top-right corner — the rail and panels run all - * the way to the window edge, and the app chrome reserves the corner via - * --wincontrols-w. Renders nothing on macOS/Linux (native decorations kept) and - * in the browser build (core stub). tao provides edge/corner resize for the - * undecorated window, so no manual resize handles are needed here. + * the way to the window edge, and the app chrome reserves the corner via the + * data-window-controls flag this sets on (consumed by windowChrome.css). + * Renders nothing on macOS/Linux (native decorations kept); not bundled in the + * browser build. tao provides edge/corner resize for the undecorated window, so + * no manual resize handles are needed here. */ export function WindowTitleBar() { const [isWindows, setIsWindows] = useState(seedIsWindows); @@ -44,21 +48,19 @@ export function WindowTitleBar() { }; }, []); - // The controls are a fixed overlay in the top-right corner. Publish their - // width so app chrome at the top-right (the tool panel header) can pad clear - // of them; unset (0) everywhere else. Layout effect so it lands before paint. + // Flag the custom chrome on so windowChrome.css can reserve the + // controls' corner across the app. Set only when active (Windows desktop); + // absent otherwise, so the skin is inert on macOS/Linux. Layout effect so it + // lands before paint. useIsomorphicEffect(() => { const root = document.documentElement; if (active) { - root.style.setProperty("--wincontrols-w", "8.625rem"); // 3 x 46px - root.style.setProperty("--wincontrols-h", "2rem"); + root.setAttribute("data-window-controls", "custom"); } else { - root.style.removeProperty("--wincontrols-w"); - root.style.removeProperty("--wincontrols-h"); + root.removeAttribute("data-window-controls"); } return () => { - root.style.removeProperty("--wincontrols-w"); - root.style.removeProperty("--wincontrols-h"); + root.removeAttribute("data-window-controls"); }; }, [active]); @@ -113,7 +115,7 @@ export function WindowTitleBar() { const appWindow = getCurrentWindow(); return ( -
+