perf(app): keep desktop sidebar ready to reopen

Retain the hidden sidebar tree so common toggles preserve local UI state while existing activity gates pause expensive hidden work.
This commit is contained in:
Mohamed Boudra
2026-07-15 11:54:59 +02:00
parent 3673846433
commit 5ddd5f3726
4 changed files with 79 additions and 26 deletions

View File

@@ -75,6 +75,9 @@ definition, no longer eligible to begin.
so its injected `collapsable={false}` reaches Android/Fabric.
- Mobile sidebars render through `MobilePanelOverlay`; do not duplicate overlay lifecycle or motion
styles in sidebar components.
- The desktop left sidebar is retained too. App chrome owns separate mounted and visible decisions:
closing it or yielding its width marks it inactive and applies `display: none` without conditionally
removing the sidebar tree.
- Animated panel nodes use React Native static styles plus inline theme values. Do not attach
Unistyles-generated styles to those nodes; Unistyles and Reanimated patching the same Fabric node
has caused native crashes.

View File

@@ -169,6 +169,47 @@ test.describe("Mobile sidebar panelState transition", () => {
test.describe("Half-screen desktop layout", () => {
test.use({ viewport: { width: 751, height: 982 } });
test("keeps the sidebar scroll position across close and reopen", async ({ page }) => {
const workspace = await seedWorkspace({ repoPrefix: "sidebar-retained-scroll-" });
try {
let lastWorkspaceId = workspace.workspaceId;
for (let index = 0; index < 24; index += 1) {
const created = await workspace.client.createWorkspace({
source: {
kind: "directory",
path: workspace.repoPath,
projectId: workspace.projectId,
},
title: `Retained sidebar ${index + 1}`,
});
if (!created.workspace) {
throw new Error(created.error ?? "Failed to fill the retained sidebar");
}
lastWorkspaceId = created.workspace.id;
}
await gotoAppShell(page);
await waitForSidebarWorkspace(page, lastWorkspaceId);
const sidebarScroll = page.getByTestId("sidebar-project-workspace-list-scroll");
const scrollTop = await sidebarScroll.evaluate((element) => {
element.scrollTop = 160;
return element.scrollTop;
});
expect(scrollTop).toBe(160);
await page.getByTestId("menu-button").click();
await expect(page.getByTestId("sidebar-global-new-workspace")).not.toBeVisible();
await page.getByTestId("menu-button").click();
await expect(page.getByTestId("sidebar-global-new-workspace")).toBeVisible();
await expect(sidebarScroll).toHaveJSProperty("scrollTop", scrollTop);
} finally {
await workspace.cleanup();
}
});
test("keeps the pinned sidebar at half of a 14-inch Mac display", async ({ page }) => {
await gotoAppShell(page);
await expect(page.getByTestId("sidebar-global-new-workspace")).toBeVisible();

View File

@@ -482,10 +482,10 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
requestedExplorerWidth: explorerWidth,
viewportWidth,
});
const desktopSidebarRendered =
const desktopSidebarMounted = chromeEnabled && !isFocusModeEnabled;
const desktopSidebarVisible =
!isCompactLayout &&
chromeEnabled &&
!isFocusModeEnabled &&
desktopSidebarMounted &&
isDesktopAgentListOpen &&
canDesktopAppSidebarShare({
contentMinimumWidth: appContentMinimumWidth,
@@ -494,13 +494,14 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
});
const hasTopLeftWindowControls = useHasWindowChromeObstruction("top-left");
const appChromeLayout = resolveDesktopAppChromeLayout({
desktopSidebarRendered,
desktopSidebarRendered: desktopSidebarVisible,
hasTopLeftWindowControls,
sidebarControlsEnabled: chromeEnabled && !isFocusModeEnabled,
});
const sidebarChrome = (
<SidebarChrome
showSidebar={isCompactLayout ? chromeEnabled : desktopSidebarRendered}
mounted={isCompactLayout ? chromeEnabled : desktopSidebarMounted}
visible={isCompactLayout ? chromeEnabled : desktopSidebarVisible}
keyboardShortcutsEnabled={keyboardShortcutsEnabled}
/>
);
@@ -567,19 +568,22 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
}
function SidebarChrome({
showSidebar,
mounted,
visible,
keyboardShortcutsEnabled,
}: {
showSidebar: boolean;
mounted: boolean;
visible: boolean;
keyboardShortcutsEnabled: boolean;
}) {
const isCompactLayout = useIsCompactFormFactor();
const isOpen = usePanelStore((state) =>
selectIsAgentListOpen(state, { isCompact: isCompactLayout }),
);
const active = visible && isOpen;
return (
<SidebarModelProvider active={showSidebar && isOpen}>
{showSidebar ? <LeftSidebar /> : null}
<SidebarModelProvider active={active}>
{mounted ? <LeftSidebar active={active} /> : null}
<WorkspaceShortcutTargetsSubscriber enabled={keyboardShortcutsEnabled} />
</SidebarModelProvider>
);

View File

@@ -51,7 +51,7 @@ import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
import { useHosts } from "@/runtime/host-runtime";
import { useActiveWorkspaceSelection } from "@/stores/navigation-active-workspace-store";
import { useWorkspace } from "@/stores/session-store-hooks";
import { selectIsAgentListOpen, usePanelStore } from "@/stores/panel-store";
import { usePanelStore } from "@/stores/panel-store";
import { useOwnsWindowChromeCorner, WindowChromeSafeArea } from "@/utils/desktop-window";
import { useCloseAgentListGesture } from "@/mobile-panels/gestures";
import { MobilePanelOverlay } from "@/mobile-panels/presentation";
@@ -117,19 +117,16 @@ interface MobileSidebarProps extends SidebarSharedProps {
interface DesktopSidebarProps extends SidebarSharedProps {
insetsTop: number;
isOpen: boolean;
active: boolean;
handleViewMore: () => void;
handleViewSchedules: () => void;
}
export const LeftSidebar = memo(function LeftSidebar() {
export const LeftSidebar = memo(function LeftSidebar({ active }: { active: boolean }) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const isCompactLayout = useIsCompactFormFactor();
const isOpen = usePanelStore((state) =>
selectIsAgentListOpen(state, { isCompact: isCompactLayout }),
);
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
const {
@@ -256,7 +253,7 @@ export const LeftSidebar = memo(function LeftSidebar() {
if (isCompactLayout) {
return (
<RetainedPanelActivity active={isOpen}>
<RetainedPanelActivity active={active}>
<MobileSidebar
{...sharedProps}
insetsTop={insets.top}
@@ -275,11 +272,11 @@ export const LeftSidebar = memo(function LeftSidebar() {
}
return (
<RetainedPanelActivity active={isOpen}>
<RetainedPanelActivity active={active}>
<DesktopSidebar
{...sharedProps}
insetsTop={insets.top}
isOpen={isOpen}
active={active}
handleOpenProject={handleOpenProjectDesktop}
handleHome={handleHomeDesktop}
handleSettings={handleSettingsDesktop}
@@ -702,7 +699,7 @@ function DesktopSidebar({
handleAddHost,
handleOpenHostSettings,
insetsTop,
isOpen,
active,
handleViewMore,
handleViewSchedules,
}: DesktopSidebarProps) {
@@ -753,8 +750,12 @@ function DesktopSidebar({
}));
const desktopSidebarStyle = useMemo(
() => [staticStyles.desktopSidebar, resizeAnimatedStyle],
[resizeAnimatedStyle],
() => [
staticStyles.desktopSidebar,
!active && staticStyles.desktopSidebarHidden,
resizeAnimatedStyle,
],
[active, resizeAnimatedStyle],
);
const desktopSidebarBorderStyle = useMemo(
() => [styles.desktopSidebarBorder, { flex: 1, paddingTop: insetsTop }],
@@ -769,12 +770,13 @@ function DesktopSidebar({
[],
);
if (!isOpen) {
return null;
}
return (
<Animated.View style={desktopSidebarStyle}>
<Animated.View
accessibilityElementsHidden={!active}
importantForAccessibility={active ? "auto" : "no-hide-descendants"}
pointerEvents={active ? "auto" : "none"}
style={desktopSidebarStyle}
>
<View style={desktopSidebarBorderStyle}>
<View style={styles.sidebarDragArea}>
{ownsTopLeft ? (
@@ -917,6 +919,9 @@ const staticStyles = RNStyleSheet.create({
desktopSidebar: {
position: "relative" as const,
},
desktopSidebarHidden: {
display: "none",
},
});
const styles = StyleSheet.create((theme) => ({