From 054997a84ae8669bd364eb8d7062255e3d21ea10 Mon Sep 17 00:00:00 2001 From: Andrew Mikofalvy <5668128+amikofalvy@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:31:17 -0700 Subject: [PATCH] fix(open-knowledge): hide bottom terminal grabber when right-docked (#2364) * fix(open-knowledge): hide bottom terminal grabber when right-docked TerminalDock gated its bottom resize handle on the visible prop alone. A right-docked terminal keeps visible true while the bottom panel sits collapsed and empty, so the grabber pill stayed rendered and draggable, pulling up an empty panel. Gate the handle (grabber render, disabled state, and the pointer-down guard) on bottom-docked-and-visible instead, and reuse the same predicate for the panel restore effect and initial size. Docking back to the bottom restores the grabber. Web and embedded hosts never mount TerminalDock (no terminal bridge), so this only changes the desktop app. * review polish: canonical dock-position type in test, tighter comments Import TerminalDockPosition from terminal-dock-store in the dock test instead of redeclaring the union locally, drop the comment sentence that restated the bottomOpen expression, and make the disabled-handle comment name where each way back in actually lives (bottom reveal tab here; EditorArea reveal tab and tab-strip dock-toggle when right-docked). GitOrigin-RevId: 240b1b101d7750e42fa561aca12b94777b1057db --- .../terminal-right-dock-bottom-grabber.md | 5 +++ .../src/components/TerminalDock.dom.test.tsx | 36 +++++++++++++++---- packages/app/src/components/TerminalDock.tsx | 25 +++++++------ 3 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 .changeset/terminal-right-dock-bottom-grabber.md diff --git a/.changeset/terminal-right-dock-bottom-grabber.md b/.changeset/terminal-right-dock-bottom-grabber.md new file mode 100644 index 00000000..d2920b64 --- /dev/null +++ b/.changeset/terminal-right-dock-bottom-grabber.md @@ -0,0 +1,5 @@ +--- +"@inkeep/open-knowledge": patch +--- + +The bottom resize grabber no longer lingers when the terminal is docked to the right column. Previously the grabber stayed visible and could be dragged up to reveal an empty bottom panel; it now disappears while the terminal is right-docked and returns when you dock the terminal back to the bottom. diff --git a/packages/app/src/components/TerminalDock.dom.test.tsx b/packages/app/src/components/TerminalDock.dom.test.tsx index 76a33cb7..8a3d0edc 100644 --- a/packages/app/src/components/TerminalDock.dom.test.tsx +++ b/packages/app/src/components/TerminalDock.dom.test.tsx @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'; import { useEffect, useRef, useState } from 'react'; import { TooltipProvider } from '@/components/ui/tooltip'; import type { OkDesktopBridge } from '@/lib/desktop-bridge-types'; +import type { TerminalDockPosition } from '@/lib/terminal-dock-store'; import { requestActiveTerminalInput } from './handoff/terminal-input-events'; const TERMINAL_PANEL_ID = 'terminal-dock-panel'; @@ -46,10 +47,11 @@ mock.module('@/components/ui/resizable', () => ({ ); }, // biome-ignore lint/suspicious/noExplicitAny: test stub - ResizableHandle: ({ onPointerDown, disabled }: any) => ( + ResizableHandle: ({ onPointerDown, disabled, withHandle }: any) => (
), @@ -153,7 +155,7 @@ function makeBridge() { } // biome-ignore lint/suspicious/noExplicitAny: test harness props -function DockHarness({ v, l, onVisibleChange, bridge, onReveal }: any) { +function DockHarness({ v, l, onVisibleChange, bridge, onReveal, dock = 'bottom' }: any) { const [bottomContainer, setBottomContainer] = useState(null); const [editorRegionEl, setEditorRegionEl] = useState(null); return ( @@ -161,7 +163,7 @@ function DockHarness({ v, l, onVisibleChange, bridge, onReveal }: any) { editorRegionEl?.focus()} - dockPosition="bottom" + dockPosition={dock} onToggleDock={() => {}} /> @@ -190,13 +192,18 @@ function renderDock( ) { const onVisibleChange = mock((_v: boolean) => {}); const { bridge, create, kill, input, viewMenuPushes, dispatchMenuAction } = makeBridge(); - const ui = (v: boolean, l?: { prompt: string; nonce: number; cli?: string } | null) => ( + const ui = ( + v: boolean, + l?: { prompt: string; nonce: number; cli?: string } | null, + dock?: TerminalDockPosition, + ) => ( ); const utils = render(ui(visible, launch)); @@ -208,8 +215,11 @@ function renderDock( input, viewMenuPushes, dispatchMenuAction, - rerender: (v: boolean, l?: { prompt: string; nonce: number; cli?: string } | null) => - utils.rerender(ui(v, l)), + rerender: ( + v: boolean, + l?: { prompt: string; nonce: number; cli?: string } | null, + dock?: TerminalDockPosition, + ) => utils.rerender(ui(v, l, dock)), }; } @@ -796,6 +806,18 @@ describe('TerminalDock multi-session', () => { 'false', ); }); + + test('hides the grabber while right-docked and restores it on return to bottom', () => { + const view = renderDock(true); + act(() => view.rerender(true, null, 'right')); + const handle = () => screen.getByTestId('terminal-resize-handle'); + expect(handle().getAttribute('data-disabled')).toBe('true'); + expect(handle().getAttribute('data-with-handle')).toBe('false'); + + act(() => view.rerender(true, null, 'bottom')); + expect(handle().getAttribute('data-disabled')).toBe('false'); + expect(handle().getAttribute('data-with-handle')).toBe('true'); + }); }); describe('TerminalSessionsHost focus-return gating across a dock move', () => { diff --git a/packages/app/src/components/TerminalDock.tsx b/packages/app/src/components/TerminalDock.tsx index 4447b30a..515347ce 100644 --- a/packages/app/src/components/TerminalDock.tsx +++ b/packages/app/src/components/TerminalDock.tsx @@ -33,6 +33,7 @@ export function TerminalDock({ const panelRef = usePanelRef(); const [isCollapsed, setIsCollapsed] = useState(!visible); const xtermBackground = xtermThemeForMode(resolvedTheme).background; + const bottomOpen = visible && dockPosition === 'bottom'; const showBottomRevealTab = !visible && dockPosition === 'bottom' && onReveal != null; const [initialHeightPx] = useState(() => getInitialTerminalHeight()); @@ -64,12 +65,12 @@ export function TerminalDock({ useEffect(() => { const panel = panelRef.current; if (panel == null) return; - if (visible && dockPosition === 'bottom') { + if (bottomOpen) { panel.resize(`${heightPxRef.current}px`); } else { panel.collapse(); } - }, [visible, panelRef, dockPosition]); + }, [bottomOpen, panelRef]); return ( - {/* The handle drags only while the terminal is open: you can resize it, and - drag all the way down to collapse (hide). Once hidden it is disabled — the - reveal tab is the single open mechanism, so there is no drag-up-to-open - (which would be a second, redundant way in). Gating on the controlled - `visible` prop (not `isCollapsed`) means an in-progress drag-to-collapse + {/* The handle drags only while the bottom panel is open: you can resize it, + and drag all the way down to collapse (hide). Otherwise it is disabled — + when bottom-docked-and-hidden the reveal tab rendered above is the single + way back in, and when right-docked the ways in live outside this file + (EditorArea's reveal tab; the tab strip's dock-toggle) — so there is no + drag-up-to-open (which would be a second, redundant way in). Gating on + controlled props (not `isCollapsed`) means an in-progress drag-to-collapse completes before the handle disables on the next commit. */} { - if (!visible) return; + if (!bottomOpen) return; setIsDragging(true); isDraggingRef.current = true; const handleUp = () => { @@ -123,7 +126,7 @@ export function TerminalDock({ id={TERMINAL_PANEL_ID} style={{ backgroundColor: xtermBackground }} panelRef={panelRef} - defaultSize={visible && dockPosition === 'bottom' ? `${initialHeightPx}px` : 0} + defaultSize={bottomOpen ? `${initialHeightPx}px` : 0} minSize="120px" maxSize="95%" collapsible