Fix desktop layout and window controls at half-screen width (#1983)

* fix(app): preserve desktop layout in half-screen windows

Treat native window controls as physical corner obstructions instead of deriving clearance from compact layout state. Keep 751px windows in desktop mode while preserving usable content when persisted sidebars are wide.

* fix(app): retain sidebar width across window resizes

* fix(app): yield app sidebar to narrow settings

* test(app): wait for mobile sidebar motion to settle

* fix(app): yield navigation to narrow workspace explorer

* fix(app): mark sidebar sizing helpers as worklets

* fix(app): hide app navigation when space is constrained
This commit is contained in:
Mohamed Boudra
2026-07-14 23:01:14 +02:00
committed by GitHub
parent 1b8af2e58e
commit 50ed0d0ab1
27 changed files with 1263 additions and 718 deletions
+64 -55
View File
@@ -32,8 +32,14 @@ import { useTranslation } from "react-i18next";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { ResizeHandle } from "@/components/resize-handle";
import { RetainedPanel } from "@/components/retained-panel";
import { resolveSplitContainerRoot } from "@/components/split-container-focus";
import { shouldFocusPaneFromEventTarget } from "@/components/split-container-pane-focus";
import { useWindowControlsPadding } from "@/utils/desktop-window";
import {
WindowChromeRegion,
WindowChromeSafeArea,
useWindowChromeCorners,
type WindowChromeCorners,
} from "@/utils/desktop-window";
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
import {
computeTabDropPreview,
@@ -158,6 +164,7 @@ interface SplitNodeViewProps extends Omit<SplitContainerProps, "layout" | "onMov
showDropZones: boolean;
dropPreview: SplitDropZoneHover | null;
tabDropPreview: TabDropPreview | null;
windowChromeCorners: WindowChromeCorners;
}
interface SplitPaneViewProps extends Omit<
@@ -169,6 +176,7 @@ interface SplitPaneViewProps extends Omit<
| "showDropZones"
| "dropPreview"
| "onResizeSplit"
| "windowChromeCorners"
> {
pane: SplitPane;
uiTabs: WorkspaceTab[];
@@ -383,6 +391,8 @@ export function SplitContainer({
renderPaneEmptyState = () => null,
focusModeEnabled,
}: SplitContainerProps) {
const inheritedWindowChromeCorners = useWindowChromeCorners();
const windowChromeCorners = focusModeEnabled ? inheritedWindowChromeCorners : "none";
const [activeDragTabId, setActiveDragTabId] = useState<string | null>(null);
const [dropPreview, setDropPreview] = useState<SplitDropZoneHover | null>(null);
const [tabDropPreview, setTabDropPreview] = useState<TabDropPreview | null>(null);
@@ -399,18 +409,16 @@ export function SplitContainer({
);
const panesById = useMemo(() => collectPanesById(layout.root), [layout.root]);
const effectiveRoot = useMemo(() => {
if (!focusModeEnabled) {
return layout.root;
}
const focusedPane = layout.focusedPaneId ? panesById.get(layout.focusedPaneId) : null;
if (!focusedPane) {
return layout.root;
}
return { kind: "pane" as const, pane: focusedPane };
}, [focusModeEnabled, layout.root, layout.focusedPaneId, panesById]);
const renderRoot = useMemo(() => wrapRootPaneForStableMount(effectiveRoot), [effectiveRoot]);
const splitRoot = useMemo(
() =>
resolveSplitContainerRoot({
root: layout.root,
focusedPaneId: layout.focusedPaneId,
focusModeEnabled,
}),
[focusModeEnabled, layout.focusedPaneId, layout.root],
);
const renderRoot = useMemo(() => wrapRootPaneForStableMount(splitRoot.root), [splitRoot.root]);
const handleDragStart = useCallback((event: DragStartEvent) => {
const data = asWorkspaceTabDragData(event.active.data.current);
@@ -565,6 +573,7 @@ export function SplitContainer({
onDragCancel={handleDragCancel}
onDragEnd={handleDragEnd}
>
{splitRoot.usesFallbackStrip && <WindowChromeSafeArea placement="below" />}
<SplitNodeView
node={renderRoot}
workspaceKey={workspaceKey}
@@ -601,6 +610,7 @@ export function SplitContainer({
showDropZones={activeDragTabId !== null}
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
windowChromeCorners={splitRoot.usesFallbackStrip ? "none" : windowChromeCorners}
/>
<DragOverlay dropAnimation={null}>
{activeDragTabId ? (
@@ -744,6 +754,7 @@ function SplitNodeView({
showDropZones,
dropPreview,
tabDropPreview,
windowChromeCorners,
}: SplitNodeViewProps) {
const groupId = node.kind === "group" ? node.group.id : null;
const groupDirection = node.kind === "group" ? node.group.direction : null;
@@ -762,41 +773,43 @@ function SplitNodeView({
if (node.kind === "pane") {
return (
<SplitPaneView
pane={node.pane}
uiTabs={uiTabs}
isFocused={node.pane.id === focusedPaneId}
normalizedServerId={normalizedServerId}
normalizedWorkspaceId={normalizedWorkspaceId}
isWorkspaceFocused={isWorkspaceFocused}
hoveredCloseTabKey={hoveredCloseTabKey}
setHoveredCloseTabKey={setHoveredCloseTabKey}
closingTabIds={closingTabIds}
onNavigateTab={onNavigateTab}
onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
onCloseTabsToRight={onCloseTabsToRight}
onCloseOtherTabs={onCloseOtherTabs}
onCreateDraftTab={onCreateDraftTab}
onCreateTerminalTab={onCreateTerminalTab}
onCreateBrowserTab={onCreateBrowserTab}
showCreateBrowserTab={showCreateBrowserTab}
buildPaneContentModel={buildPaneContentModel}
onFocusPane={onFocusPane}
onSplitPane={onSplitPane}
onSplitPaneEmpty={onSplitPaneEmpty}
onReorderTabsInPane={onReorderTabsInPane}
renderPaneEmptyState={renderPaneEmptyState}
activeDragTabId={activeDragTabId}
showDropZones={showDropZones}
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
/>
<WindowChromeRegion corners={windowChromeCorners}>
<SplitPaneView
pane={node.pane}
uiTabs={uiTabs}
isFocused={node.pane.id === focusedPaneId}
normalizedServerId={normalizedServerId}
normalizedWorkspaceId={normalizedWorkspaceId}
isWorkspaceFocused={isWorkspaceFocused}
hoveredCloseTabKey={hoveredCloseTabKey}
setHoveredCloseTabKey={setHoveredCloseTabKey}
closingTabIds={closingTabIds}
onNavigateTab={onNavigateTab}
onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
onCloseTabsToRight={onCloseTabsToRight}
onCloseOtherTabs={onCloseOtherTabs}
onCreateDraftTab={onCreateDraftTab}
onCreateTerminalTab={onCreateTerminalTab}
onCreateBrowserTab={onCreateBrowserTab}
showCreateBrowserTab={showCreateBrowserTab}
buildPaneContentModel={buildPaneContentModel}
onFocusPane={onFocusPane}
onSplitPane={onSplitPane}
onSplitPaneEmpty={onSplitPaneEmpty}
onReorderTabsInPane={onReorderTabsInPane}
renderPaneEmptyState={renderPaneEmptyState}
activeDragTabId={activeDragTabId}
showDropZones={showDropZones}
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
/>
</WindowChromeRegion>
);
}
@@ -843,6 +856,7 @@ function SplitNodeView({
showDropZones={showDropZones}
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
windowChromeCorners={windowChromeCorners}
/>
</SplitGroupChild>
{index < node.group.children.length - 1 ? (
@@ -898,7 +912,6 @@ function SplitPaneView({
const { theme: _theme } = useUnistyles();
const paneRef = useRef<View | null>(null);
const stableOnFocusPane = useStableEvent(onFocusPane);
const padding = useWindowControlsPadding("tabRow");
const paneState = useMemo(
() =>
deriveWorkspacePaneState({
@@ -995,15 +1008,11 @@ function SplitPaneView({
() => onSplitPaneEmpty({ targetPaneId: paneId, position: "bottom" }),
[onSplitPaneEmpty, paneId],
);
const paneTabsStyle = useMemo(
() => [styles.paneTabs, { paddingLeft: padding.left, paddingRight: padding.right }],
[padding.left, padding.right],
);
return (
<RenderProfile id={`SplitPaneView:${pane.id}`}>
<View ref={paneRef} collapsable={false} style={styles.pane}>
<View style={paneTabsStyle}>
<WindowChromeSafeArea placement="inline" style={styles.paneTabs}>
<TitlebarDragRegion />
<WorkspaceDesktopTabsRow
paneId={pane.id}
@@ -1035,7 +1044,7 @@ function SplitPaneView({
tabDropPreview?.paneId === pane.id ? tabDropPreview.indicatorIndex : null
}
/>
</View>
</WindowChromeSafeArea>
<View style={styles.paneContent}>
{mountedPaneTabIds.length > 0