Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/terminal-right-dock-bottom-grabber.md
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 29 additions & 7 deletions packages/app/src/components/TerminalDock.dom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => (
<div
data-testid="terminal-resize-handle"
data-disabled={disabled ? 'true' : 'false'}
data-with-handle={withHandle ? 'true' : 'false'}
onPointerDown={onPointerDown}
/>
),
Expand Down Expand Up @@ -153,15 +155,15 @@ 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<HTMLDivElement | null>(null);
const [editorRegionEl, setEditorRegionEl] = useState<HTMLDivElement | null>(null);
return (
<TooltipProvider>
<TerminalDock
visible={v}
onVisibleChange={onVisibleChange}
dockPosition="bottom"
dockPosition={dock}
onBottomContainer={setBottomContainer}
onEditorRegion={setEditorRegionEl}
onReveal={onReveal}
Expand All @@ -176,7 +178,7 @@ function DockHarness({ v, l, onVisibleChange, bridge, onReveal }: any) {
container={bottomContainer}
isShowing={v && bottomContainer != null}
onRequestEditorFocus={() => editorRegionEl?.focus()}
dockPosition="bottom"
dockPosition={dock}
onToggleDock={() => {}}
/>
</TooltipProvider>
Expand All @@ -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,
) => (
<DockHarness
v={v}
l={l ?? null}
onVisibleChange={onVisibleChange}
bridge={bridge}
onReveal={onReveal}
dock={dock ?? 'bottom'}
/>
);
const utils = render(ui(visible, launch));
Expand All @@ -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)),
};
}

Expand Down Expand Up @@ -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', () => {
Expand Down
25 changes: 14 additions & 11 deletions packages/app/src/components/TerminalDock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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 (
<ResizablePanelGroup
Expand All @@ -96,17 +97,19 @@ export function TerminalDock({
) : null}
</div>
</ResizablePanel>
{/* 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. */}
<ResizableHandle
withHandle={visible}
disabled={!visible}
withHandle={bottomOpen}
disabled={!bottomOpen}
onPointerDown={() => {
if (!visible) return;
if (!bottomOpen) return;
setIsDragging(true);
isDraggingRef.current = true;
const handleUp = () => {
Expand All @@ -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
Expand Down