(props)
| 104 | ); |
| 105 | |
| 106 | export const WorkspaceShell: React.FC<WorkspaceShellProps> = (props) => { |
| 107 | const shellRef = useRef<HTMLDivElement>(null); |
| 108 | const shellSize = useResizeObserver(shellRef); |
| 109 | |
| 110 | // WorkspaceShell switches to flex-col at this breakpoint, so in that stacked mode the |
| 111 | // right sidebar doesn't need to "leave room" for ChatPane beside it. |
| 112 | const isStacked = |
| 113 | typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches; |
| 114 | |
| 115 | const [persistedLeftSidebarWidthPx] = usePersistedState<unknown>( |
| 116 | LEFT_SIDEBAR_WIDTH_KEY, |
| 117 | LEFT_SIDEBAR_DEFAULT_WIDTH_PX, |
| 118 | { listener: true } |
| 119 | ); |
| 120 | const containerWidthPx = shellSize?.width ?? 0; |
| 121 | // Before ResizeObserver reports the real shell width, estimate it from the persisted left |
| 122 | // sidebar width so a wide right sidebar doesn't first paint at a viewport-wide clamp and |
| 123 | // then visibly snap narrower once the shell is measured. |
| 124 | const usableWidthPx = |
| 125 | containerWidthPx > 0 |
| 126 | ? containerWidthPx |
| 127 | : estimateWorkspaceShellFallbackWidthPx({ |
| 128 | viewportWidthPx: typeof window !== "undefined" ? window.innerWidth : 1200, |
| 129 | isStacked, |
| 130 | leftSidebarCollapsed: props.leftSidebarCollapsed, |
| 131 | persistedLeftSidebarWidthPx, |
| 132 | }); |
| 133 | |
| 134 | // Prevent ChatPane + RightSidebar from overflowing the workspace shell (which would show a |
| 135 | // horizontal scrollbar due to WorkspaceShell's `overflow-x-auto`). |
| 136 | const effectiveMaxWidthPx = isStacked |
| 137 | ? RIGHT_SIDEBAR_ABS_MAX_WIDTH_PX |
| 138 | : Math.min( |
| 139 | RIGHT_SIDEBAR_ABS_MAX_WIDTH_PX, |
| 140 | Math.max( |
| 141 | RIGHT_SIDEBAR_MIN_WIDTH_PX, |
| 142 | usableWidthPx - CHAT_PANE_MIN_WIDTH_PX - RIGHT_SIDEBAR_OVERFLOW_GUARD_PX |
| 143 | ) |
| 144 | ); |
| 145 | |
| 146 | const sidebar = useResizableSidebar({ |
| 147 | enabled: true, |
| 148 | defaultWidth: RIGHT_SIDEBAR_DEFAULT_WIDTH_PX, |
| 149 | minWidth: RIGHT_SIDEBAR_MIN_WIDTH_PX, |
| 150 | maxWidth: effectiveMaxWidthPx, |
| 151 | storageKey: RIGHT_SIDEBAR_WIDTH_KEY, |
| 152 | }); |
| 153 | |
| 154 | const { width: sidebarWidth, isResizing, startResize } = sidebar; |
| 155 | const addTerminalRef = useRef<((options?: TerminalSessionCreateOptions) => void) | null>(null); |
| 156 | const openTerminalPopout = useOpenTerminal(); |
| 157 | const handleOpenTerminal = useCallback( |
| 158 | (options?: TerminalSessionCreateOptions) => { |
| 159 | // On mobile touch devices, always use popout since the right sidebar is hidden |
| 160 | const isMobileTouch = window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches; |
| 161 | if (isMobileTouch) { |
| 162 | void openTerminalPopout(props.workspaceId, props.runtimeConfig, options); |
| 163 | } else { |
nothing calls this directly
no test coverage detected