diff --git a/apps/app/src/App.tsx b/apps/app/src/App.tsx index 84a192c49..2283307a3 100644 --- a/apps/app/src/App.tsx +++ b/apps/app/src/App.tsx @@ -5,6 +5,7 @@ import { AuthCallbackView } from "./views/AuthCallbackView"; import { RootComposeRoute } from "./views/RootComposeView"; import { QuickCreateProjectProvider } from "./hooks/useQuickCreateProject"; import { ProviderCliHealthToasts } from "./components/provider-cli/ProviderCliHealthToasts"; +import { useDesktopThemeSync } from "./hooks/useDesktopThemeSync"; import { useDesktopUpdateAvailableToast, useUpdateAvailableToast, @@ -96,6 +97,9 @@ export function App() { useUpdateAvailableToast(); // Show a separate toast when the Electron shell reports a desktop update. useDesktopUpdateAvailableToast(); + // Keep the Electron window chrome (traffic lights, inactive title bar) + // in sync with bb's resolved theme. + useDesktopThemeSync(); return ( diff --git a/apps/app/src/app.css b/apps/app/src/app.css index fc8ab8b5b..c8ae2135e 100644 --- a/apps/app/src/app.css +++ b/apps/app/src/app.css @@ -117,9 +117,23 @@ * toast in the stack. */ :where( - [data-sonner-toast].bb-app-toast[data-expanded="false"][data-front="false"] - ) { + [data-sonner-toast].bb-app-toast[data-expanded="false"][data-front="false"] + ) { overflow: hidden; border-radius: var(--radius-md); } + + /* + * Opt out of the global thin scrollbar for horizontally-scrolling strips + * (e.g. the secondary-panel tab strip) where the scrollbar would clutter a + * short row. The element still scrolls; only the scrollbar chrome is hidden. + */ + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .no-scrollbar::-webkit-scrollbar { + display: none; + } } diff --git a/apps/app/src/components/layout/AppLayout.test.tsx b/apps/app/src/components/layout/AppLayout.test.tsx index e5e8629b9..06666e594 100644 --- a/apps/app/src/components/layout/AppLayout.test.tsx +++ b/apps/app/src/components/layout/AppLayout.test.tsx @@ -6,6 +6,7 @@ import { fireEvent, render, screen, + waitFor, within, } from "@testing-library/react"; import { Suspense, type ReactNode } from "react"; @@ -16,6 +17,7 @@ import type { BbDesktopInfoChangeHandler, SystemConfigResponse, } from "@bb/server-contract"; +import { createNoopDesktopBrowserApi } from "@/test/bb-desktop-test-utils"; import { afterEach, describe, expect, it } from "vitest"; import { QuickCreateProjectProvider } from "@/hooks/useQuickCreateProject"; import { createQueryClientTestHarness } from "@/test/queryClientTestHarness"; @@ -32,6 +34,7 @@ import { } from "@/lib/bb-desktop"; interface RenderAppLayoutArgs { + children?: ReactNode; desktopInfo: BbDesktopApi | null; initialEntry: string; } @@ -40,6 +43,11 @@ interface TestProvidersProps { children: ReactNode; } +interface SidebarResizeEndScenario { + name: string; + finishDrag: () => void; +} + const testSystemConfig: SystemConfigResponse = { featureFlags: { askUserQuestion: false, @@ -52,6 +60,7 @@ const testSystemConfig: SystemConfigResponse = { function createBbDesktopApi(info: BbDesktopInfo): BbDesktopApi { return { ...info, + browser: createNoopDesktopBrowserApi(), async checkForUpdates() { return info; }, @@ -64,6 +73,9 @@ function createBbDesktopApi(info: BbDesktopInfo): BbDesktopApi { onChange(_listener: BbDesktopInfoChangeHandler) { return () => undefined; }, + setTheme() { + // no-op + }, }; } @@ -113,7 +125,7 @@ async function renderAppLayout(args: RenderAppLayoutArgs): Promise { path="*" element={ -
Layout content
+ {args.children ??
Layout content
}
} /> @@ -313,4 +325,68 @@ describe("AppLayout desktop chrome", () => { MACOS_SIDEBAR_TRIGGER_OFFSET_CLASS, ); }); + + it.each([ + { + name: "mouseup", + finishDrag: () => { + fireEvent.mouseUp(window, { clientX: 360 }); + }, + }, + { + name: "window blur", + finishDrag: () => { + fireEvent.blur(window); + }, + }, + { + name: "Escape", + finishDrag: () => { + fireEvent.keyDown(window, { key: "Escape" }); + }, + }, + ])( + "disables iframe pointer events while sidebar resize is active and restores them after $name", + async ({ finishDrag }) => { + const iframePointerEventsNoneClass = "[&_iframe]:pointer-events-none"; + + await renderAppLayout({ + desktopInfo: null, + initialEntry: "/projects/proj_sidebar_resize", + children: