diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 56b71523..c3e8ea00 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -23,13 +23,12 @@ import { useHunkSessionBridge } from "./hooks/useHunkSessionBridge"; import { useMenuController } from "./hooks/useMenuController"; import { useReviewController } from "./hooks/useReviewController"; import { buildAppMenus } from "./lib/appMenus"; +import type { FocusArea } from "./lib/focus"; import { fileRowId } from "./lib/ids"; import { resolveResponsiveLayout } from "./lib/responsive"; import { resizeSidebarWidth } from "./lib/sidebar"; import { resolveTheme, THEMES } from "./themes"; -type FocusArea = "files" | "filter"; - const FAST_CODE_HORIZONTAL_SCROLL_COLUMNS = 8; const LazyHelpDialog = lazy(async () => ({ @@ -113,7 +112,7 @@ export function App({ const [sidebarVisible, setSidebarVisible] = useState(() => !pagerMode); const [forceSidebarOpen, setForceSidebarOpen] = useState(false); const [showHelp, setShowHelp] = useState(false); - const [focusArea, setFocusArea] = useState("files"); + const [focusArea, setFocusArea] = useState("diff"); const [sidebarWidth, setSidebarWidth] = useState(34); const [resizeDragOriginX, setResizeDragOriginX] = useState(null); const [resizeStartWidth, setResizeStartWidth] = useState(null); @@ -455,9 +454,11 @@ export function App({ setFocusArea("filter"); }, []); - /** Toggle keyboard focus between the file list and the file filter. */ + // Tab cycles keyboard focus between the sidebar and the diff stream. The filter + // input is reached with `/`; Tab inside that input stays in the input so users + // can type the literal character without being kicked out. const toggleFocusArea = useCallback(() => { - setFocusArea((current) => (current === "files" ? "filter" : "files")); + setFocusArea((current) => (current === "diff" ? "files" : "diff")); }, []); /** Cycle through the available built-in themes. */ @@ -548,6 +549,7 @@ export function App({ focusArea, focusFilter, moveToAnnotatedHunk, + moveToFile: review.moveToFile, moveToHunk: review.moveToHunk, moveMenuItem, openMenu, @@ -673,6 +675,7 @@ export function App({ <> { await setup.mockInput.pressKey("F10"); }); let frame = await waitForFrame(setup, (currentFrame) => - currentFrame.includes("Toggle files/filter focus"), + currentFrame.includes("Toggle files/diff focus"), ); - if (!frame.includes("Toggle files/filter focus")) { + if (!frame.includes("Toggle files/diff focus")) { await act(async () => { await setup.mockInput.pressKey("F10"); }); frame = await waitForFrame(setup, (currentFrame) => - currentFrame.includes("Toggle files/filter focus"), + currentFrame.includes("Toggle files/diff focus"), ); } - expect(frame).toContain("Toggle files/filter focus"); + expect(frame).toContain("Toggle files/diff focus"); expect(frame).toContain("Reload"); expect(frame).toContain("Quit"); @@ -1690,6 +1690,57 @@ describe("App interactions", () => { } }); + test("Tab cycles focus between sidebar and diff, and ↓ steps the file selection while sidebar holds focus", async () => { + const { getLatestSnapshot, hostClient } = createMockHostClient(); + const setup = await testRender( + , + { width: 240, height: 24 }, + ); + + try { + await flush(setup); + + // Default focus is "diff": pressing ↓ scrolls the diff and leaves the + // file selection on alpha.ts. + expect(getLatestSnapshot()).toMatchObject({ selectedFilePath: "alpha.ts" }); + await act(async () => { + await setup.mockInput.pressArrow("down"); + }); + await flush(setup); + expect(getLatestSnapshot()).toMatchObject({ selectedFilePath: "alpha.ts" }); + + // Tab once → sidebar holds focus, ↓ steps to the next visible file. + await act(async () => { + await setup.mockInput.pressTab(); + }); + await flush(setup); + await act(async () => { + await setup.mockInput.pressArrow("down"); + }); + const snapshotOnFiles = await waitForSnapshot( + setup, + getLatestSnapshot, + (snapshot) => snapshot.selectedFilePath === "beta.ts", + ); + expect(snapshotOnFiles).toMatchObject({ selectedFilePath: "beta.ts" }); + + // Tab again → focus returns to the diff; ↓ no longer moves files. + await act(async () => { + await setup.mockInput.pressTab(); + }); + await flush(setup); + await act(async () => { + await setup.mockInput.pressArrow("down"); + }); + await flush(setup); + expect(getLatestSnapshot()).toMatchObject({ selectedFilePath: "beta.ts" }); + } finally { + await act(async () => { + setup.renderer.destroy(); + }); + } + }); + test("filter focus accepts typed input and narrows the visible file set", async () => { const setup = await testRender(, { width: 240, @@ -1700,7 +1751,7 @@ describe("App interactions", () => { await flush(setup); await act(async () => { - await setup.mockInput.pressTab(); + await setup.mockInput.typeText("/"); }); await flush(setup); await act(async () => { @@ -1729,7 +1780,7 @@ describe("App interactions", () => { await flush(setup); await act(async () => { - await setup.mockInput.pressTab(); + await setup.mockInput.typeText("/"); }); await flush(setup); await act(async () => { @@ -1744,7 +1795,7 @@ describe("App interactions", () => { expect(frame).not.toContain("Annotation for alpha.ts"); await act(async () => { - await setup.mockInput.pressTab(); + await setup.mockInput.pressEnter(); }); await flush(setup); @@ -1772,7 +1823,7 @@ describe("App interactions", () => { await flush(setup); await act(async () => { - await setup.mockInput.pressTab(); + await setup.mockInput.typeText("/"); }); await flush(setup); await act(async () => { @@ -1863,7 +1914,7 @@ describe("App interactions", () => { await flush(setup); let frame = setup.captureCharFrame(); - expect(frame).toContain("Toggle files/filter focus"); + expect(frame).toContain("Toggle files/diff focus"); expect(frame).not.toContain("Controls help"); await act(async () => { @@ -1873,7 +1924,7 @@ describe("App interactions", () => { frame = setup.captureCharFrame(); expect(frame).toContain("Controls help"); - expect(frame).not.toContain("Toggle files/filter focus"); + expect(frame).not.toContain("Toggle files/diff focus"); await act(async () => { await setup.mockInput.pressArrow("right"); @@ -1881,7 +1932,7 @@ describe("App interactions", () => { await flush(setup); frame = setup.captureCharFrame(); - expect(frame).toContain("Toggle files/filter focus"); + expect(frame).toContain("Toggle files/diff focus"); expect(frame).not.toContain("Controls help"); } finally { await act(async () => { diff --git a/src/ui/AppHost.responsive.test.tsx b/src/ui/AppHost.responsive.test.tsx index b26f10bf..ddcae131 100644 --- a/src/ui/AppHost.responsive.test.tsx +++ b/src/ui/AppHost.responsive.test.tsx @@ -216,7 +216,7 @@ describe("responsive app", () => { try { await act(async () => { await setup.renderOnce(); - await setup.mockInput.pressTab(); + await setup.mockInput.typeText("/"); await setup.renderOnce(); }); diff --git a/src/ui/components/chrome/HelpDialog.tsx b/src/ui/components/chrome/HelpDialog.tsx index 1ed1f595..bc52b464 100644 --- a/src/ui/components/chrome/HelpDialog.tsx +++ b/src/ui/components/chrome/HelpDialog.tsx @@ -51,7 +51,7 @@ export function HelpDialog({ title: "Review", items: [ ["/", "focus file filter"], - ["Tab", "toggle files/filter focus"], + ["Tab", "toggle files/diff focus"], ["F10", "open menus"], [canRefresh ? "r / q" : "q", canRefresh ? "reload / quit" : "quit"], ], diff --git a/src/ui/components/panes/PaneDivider.tsx b/src/ui/components/panes/PaneDivider.tsx index c6a879ad..2836ba1c 100644 --- a/src/ui/components/panes/PaneDivider.tsx +++ b/src/ui/components/panes/PaneDivider.tsx @@ -5,6 +5,7 @@ import type { AppTheme } from "../../themes"; export function PaneDivider({ dividerHitLeft, dividerHitWidth, + highlighted = false, isResizing, theme, onMouseDown, @@ -14,6 +15,7 @@ export function PaneDivider({ }: { dividerHitLeft: number; dividerHitWidth: number; + highlighted?: boolean; isResizing: boolean; theme: AppTheme; onMouseDown: (event: TuiMouseEvent) => void; @@ -21,13 +23,14 @@ export function PaneDivider({ onMouseDragEnd: (event: TuiMouseEvent) => void; onMouseUp: (event: TuiMouseEvent) => void; }) { + const accent = isResizing || highlighted; return ( <> ; selectedFileId?: string; textWidth: number; @@ -30,7 +32,7 @@ export function SidebarPane({ style={{ width, border: ["top"], - borderColor: theme.border, + borderColor: focused ? theme.accent : theme.border, backgroundColor: theme.panel, paddingY: 1, paddingX: 0, diff --git a/src/ui/components/ui-components.test.tsx b/src/ui/components/ui-components.test.tsx index 668a3f15..0a26b2b1 100644 --- a/src/ui/components/ui-components.test.tsx +++ b/src/ui/components/ui-components.test.tsx @@ -335,6 +335,37 @@ async function captureFrame(node: ReactNode, width = 120, height = 24) { } } +async function captureSpansFrame(node: ReactNode, width = 120, height = 24) { + const setup = await testRender(node, { width, height }); + + try { + await act(async () => { + await setup.renderOnce(); + }); + + return setup.captureSpans(); + } finally { + await act(async () => { + setup.renderer.destroy(); + }); + } +} + +/** Pull the foreground colour of the first horizontal-border character on the + * top row of the rendered output. */ +function topBorderColor(frame: { + lines: Array<{ spans: Array<{ text: string; fg?: { buffer?: ArrayLike } }> }>; +}) { + const topLine = frame.lines[0]; + if (!topLine) return null; + for (const span of topLine.spans) { + if (/[─━]/.test(span.text)) { + return capturedColorToHex(span.fg); + } + } + return null; +} + function frameHasHighlightedMarker( frame: { lines: Array<{ spans: Array<{ text: string; fg?: unknown; bg?: unknown }> }> }, marker: string, @@ -439,6 +470,7 @@ describe("UI components", () => { const frame = await captureFrame( { expect(frame).not.toContain("M +2 -1 AI"); }); + test("SidebarPane top-border colour shifts to theme.accent when focused", async () => { + const theme = resolveTheme("midnight", null); + const files = [ + createTestDiffFile( + "app", + "src/ui/App.tsx", + "export const app = 1;\n", + "export const app = 2;\n", + ), + ]; + + const blurred = await captureSpansFrame( + {}} + />, + 36, + 6, + ); + const focused = await captureSpansFrame( + {}} + />, + 36, + 6, + ); + + const blurredColor = topBorderColor(blurred); + const focusedColor = topBorderColor(focused); + expect(blurredColor).not.toBeNull(); + expect(focusedColor).not.toBeNull(); + expect(focusedColor).not.toBe(blurredColor); + }); + test("DiffPane renders all diff sections in file order", async () => { const bootstrap = createBootstrap(); const theme = resolveTheme("midnight", null); @@ -1618,7 +1697,7 @@ describe("UI components", () => { "l / w / m lines / wrap / metadata", "Review", "/ focus file filter", - "Tab toggle files/filter focus", + "Tab toggle files/diff focus", "F10 open menus", "r / q reload / quit", ] as const; diff --git a/src/ui/hooks/useAppKeyboardShortcuts.ts b/src/ui/hooks/useAppKeyboardShortcuts.ts index f2349d9e..45953d51 100644 --- a/src/ui/hooks/useAppKeyboardShortcuts.ts +++ b/src/ui/hooks/useAppKeyboardShortcuts.ts @@ -3,6 +3,7 @@ import { useKeyboard } from "@opentui/react"; import { useRef } from "react"; import type { LayoutMode } from "../../core/types"; import type { MenuId } from "../components/chrome/menu"; +import type { FocusArea } from "../lib/focus"; import { isEscapeKey, isHalfPageDownKey, @@ -14,7 +15,6 @@ import { isStepUpKey, } from "../lib/keyboard"; -type FocusArea = "files" | "filter"; type ScrollUnit = "step" | "viewport" | "content" | "half"; const FAST_CODE_HORIZONTAL_SCROLL_COLUMNS = 8; @@ -29,6 +29,7 @@ export interface UseAppKeyboardShortcutsOptions { focusArea: FocusArea; focusFilter: () => void; moveToAnnotatedHunk: (delta: number) => void; + moveToFile: (delta: number) => void; moveToHunk: (delta: number) => void; moveMenuItem: (delta: number) => void; openMenu: (menuId: MenuId) => void; @@ -60,6 +61,7 @@ export function useAppKeyboardShortcuts({ focusArea, focusFilter, moveToAnnotatedHunk, + moveToFile, moveToHunk, moveMenuItem, openMenu, @@ -230,12 +232,10 @@ export function useAppKeyboardShortcuts({ return false; } - if (key.name === "tab") { - toggleFocusArea(); - return true; - } - - // Let the focused input own filter editing and escape handling. + // The focused input owns every keystroke (including Tab) so users can type + // literal characters. Esc handling lives in StatusBar so it can + // preventDefault before the input swallows it as text. + void key; return true; }; @@ -296,13 +296,24 @@ export function useAppKeyboardShortcuts({ return; } + // While the sidebar owns focus, treat step-up/down as file navigation rather than + // diff scrolling — the diff still follows because selectFile aligns the new file + // header to the top. if (isStepUpKey(key)) { - scrollDiff(-1, "step"); + if (focusAreaRef.current === "files") { + moveToFile(-1); + } else { + scrollDiff(-1, "step"); + } return; } if (isStepDownKey(key)) { - scrollDiff(1, "step"); + if (focusAreaRef.current === "files") { + moveToFile(1); + } else { + scrollDiff(1, "step"); + } return; } diff --git a/src/ui/hooks/useReviewController.test.tsx b/src/ui/hooks/useReviewController.test.tsx index d0885f59..18e18ed7 100644 --- a/src/ui/hooks/useReviewController.test.tsx +++ b/src/ui/hooks/useReviewController.test.tsx @@ -363,4 +363,55 @@ describe("useReviewController", () => { }); } }); + + test("moveToFile steps the selection forward and back across visible files", async () => { + const controllerRef: { current: ReviewController | null } = { current: null }; + const setup = await testRender( + { + controllerRef.current = nextController; + }} + />, + { width: 80, height: 4 }, + ); + + try { + await flush(setup); + expect(expectValue(controllerRef.current).selectedFileId).toBe("alpha"); + + await act(async () => { + expectValue(controllerRef.current).moveToFile(1); + }); + await flush(setup); + expect(expectValue(controllerRef.current).selectedFileId).toBe("beta"); + + await act(async () => { + expectValue(controllerRef.current).moveToFile(1); + }); + await flush(setup); + expect(expectValue(controllerRef.current).selectedFileId).toBe("gamma"); + + // Past the last file the selection clamps; we don't wrap around. + await act(async () => { + expectValue(controllerRef.current).moveToFile(1); + }); + await flush(setup); + expect(expectValue(controllerRef.current).selectedFileId).toBe("gamma"); + + await act(async () => { + expectValue(controllerRef.current).moveToFile(-2); + }); + await flush(setup); + expect(expectValue(controllerRef.current).selectedFileId).toBe("alpha"); + } finally { + await act(async () => { + setup.renderer.destroy(); + }); + } + }); }); diff --git a/src/ui/hooks/useReviewController.ts b/src/ui/hooks/useReviewController.ts index cd2508dc..5a540629 100644 --- a/src/ui/hooks/useReviewController.ts +++ b/src/ui/hooks/useReviewController.ts @@ -32,6 +32,7 @@ import type { RemovedCommentResult, SessionLiveCommentSummary, } from "../../hunk-session/types"; +import { stepFileInList } from "../lib/files"; import { findNextHunkCursor } from "../lib/hunks"; import { buildReviewState, @@ -60,6 +61,7 @@ export interface ReviewController { liveCommentsByFileId: Record; moveToAnnotatedFile: (delta: number) => void; moveToAnnotatedHunk: (delta: number) => void; + moveToFile: (delta: number) => void; moveToHunk: (delta: number) => void; scrollToNote: boolean; selectedFile: DiffFile | undefined; @@ -247,6 +249,18 @@ export function useReviewController({ files }: { files: DiffFile[] }): ReviewCon [selectFile, selectedFile?.id, visibleFiles], ); + /** Step the file selection by `delta` across the currently visible review stream. */ + const moveToFile = useCallback( + (delta: number) => { + const nextFile = stepFileInList(visibleFiles, selectedFile?.id, delta, "clamp"); + if (!nextFile) { + return; + } + selectFile(nextFile.id, 0, { alignFileHeaderTop: true }); + }, + [selectFile, selectedFile?.id, visibleFiles], + ); + /** Clear the active file filter without touching the current selection. */ const clearFilter = useCallback(() => { setFilter(""); @@ -504,6 +518,7 @@ export function useReviewController({ files }: { files: DiffFile[] }): ReviewCon clearLiveComments, moveToAnnotatedFile, moveToAnnotatedHunk, + moveToFile, moveToHunk, navigateToLocation, removeLiveComment, diff --git a/src/ui/lib/appMenus.ts b/src/ui/lib/appMenus.ts index 058bb33c..89f735cf 100644 --- a/src/ui/lib/appMenus.ts +++ b/src/ui/lib/appMenus.ts @@ -66,7 +66,7 @@ export function buildAppMenus({ const fileMenuEntries: MenuEntry[] = [ { kind: "item", - label: "Toggle files/filter focus", + label: "Toggle files/diff focus", hint: "Tab", action: toggleFocusArea, }, diff --git a/src/ui/lib/files.test.ts b/src/ui/lib/files.test.ts index 4e2b653f..94045320 100644 --- a/src/ui/lib/files.test.ts +++ b/src/ui/lib/files.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { createTestDiffFile, lines } from "../../../test/helpers/diff-helpers"; -import { buildSidebarEntries, fileLabelParts } from "./files"; +import { buildSidebarEntries, fileLabelParts, stepFileInList } from "./files"; describe("files helpers", () => { test("buildSidebarEntries hides zero-value sidebar stats", () => { @@ -117,6 +117,59 @@ describe("files helpers", () => { }); }); + describe("stepFileInList", () => { + const stub = (id: string) => + ({ id }) as unknown as Parameters[0][number]; + + test("returns null for an empty list", () => { + expect(stepFileInList([], "anything", 1, "clamp")).toBeNull(); + expect(stepFileInList([], "anything", -1, "wrap")).toBeNull(); + }); + + test("returns null when delta is zero", () => { + const files = [stub("a"), stub("b")]; + expect(stepFileInList(files, "a", 0, "clamp")).toBeNull(); + }); + + test("clamp mode steps forward and backward", () => { + const files = [stub("a"), stub("b"), stub("c")]; + expect(stepFileInList(files, "a", 1, "clamp")?.id).toBe("b"); + expect(stepFileInList(files, "b", 1, "clamp")?.id).toBe("c"); + expect(stepFileInList(files, "b", -1, "clamp")?.id).toBe("a"); + }); + + test("clamp mode returns null at the boundary instead of moving", () => { + const files = [stub("a"), stub("b"), stub("c")]; + expect(stepFileInList(files, "c", 1, "clamp")).toBeNull(); + expect(stepFileInList(files, "a", -1, "clamp")).toBeNull(); + }); + + test("clamp mode lands on the first file when nothing is selected", () => { + // Edge case the inline implementation got wrong: a forward step with no + // current selection used to skip index 0 because baseIndex defaulted to 0. + const files = [stub("a"), stub("b"), stub("c")]; + expect(stepFileInList(files, undefined, 1, "clamp")?.id).toBe("a"); + expect(stepFileInList(files, "missing", 1, "clamp")?.id).toBe("a"); + }); + + test("clamp mode lands on the last file when stepping back with nothing selected", () => { + const files = [stub("a"), stub("b"), stub("c")]; + expect(stepFileInList(files, undefined, -1, "clamp")?.id).toBe("c"); + }); + + test("wrap mode cycles past either end", () => { + const files = [stub("a"), stub("b"), stub("c")]; + expect(stepFileInList(files, "c", 1, "wrap")?.id).toBe("a"); + expect(stepFileInList(files, "a", -1, "wrap")?.id).toBe("c"); + }); + + test("wrap mode returns null when only one file exists", () => { + const files = [stub("solo")]; + expect(stepFileInList(files, "solo", 1, "wrap")).toBeNull(); + expect(stepFileInList(files, "solo", -1, "wrap")).toBeNull(); + }); + }); + test("fileLabelParts strips parser-added line endings from rename labels", () => { const renamedAcrossDirectories = { ...createTestDiffFile({ diff --git a/src/ui/lib/files.ts b/src/ui/lib/files.ts index 562860fc..d9ad5512 100644 --- a/src/ui/lib/files.ts +++ b/src/ui/lib/files.ts @@ -154,6 +154,46 @@ export function buildSidebarEntries(files: DiffFile[]): SidebarEntry[] { return entries; } +/** + * Step through `files` by `delta` based on `currentFileId`. + * + * Returns the file the new selection should land on, or `null` if no movement + * is possible (empty list, "clamp" already at the boundary, or a single-item + * list cycled in "wrap" mode). + * + * When `currentFileId` is not in the list, the cursor is treated as sitting + * just outside the list in the direction opposite `delta`, so the first + * forward step lands on index 0 (not 1) and the first backward step lands on + * the last entry — what the user expects when nothing is currently selected. + */ +export function stepFileInList( + files: DiffFile[], + currentFileId: string | undefined, + delta: number, + mode: "clamp" | "wrap", +): DiffFile | null { + if (files.length === 0 || delta === 0) { + return null; + } + + const currentIndex = files.findIndex((file) => file.id === currentFileId); + const baseIndex = currentIndex < 0 ? (delta > 0 ? -1 : files.length) : currentIndex; + + let nextIndex: number; + if (mode === "wrap") { + const length = files.length; + nextIndex = (((baseIndex + delta) % length) + length) % length; + } else { + nextIndex = Math.min(Math.max(0, baseIndex + delta), files.length - 1); + } + + if (nextIndex === currentIndex) { + return null; + } + + return files[nextIndex] ?? null; +} + /** Build the canonical file label used across headers and note cards. */ export function fileLabel(file: DiffFile | undefined) { const { filename, stateLabel } = fileLabelParts(file); diff --git a/src/ui/lib/focus.ts b/src/ui/lib/focus.ts new file mode 100644 index 00000000..80d6580f --- /dev/null +++ b/src/ui/lib/focus.ts @@ -0,0 +1,12 @@ +/** + * Which app surface currently owns keyboard input. + * + * - `"diff"`: the main review stream (default on launch). Step keys scroll + * the diff one row at a time. + * - `"files"`: the sidebar file list. Step keys move the file selection + * instead of scrolling. + * - `"filter"`: the file filter input is active. The input swallows every + * keystroke (including Tab) so users can type literal characters; Esc + * exits the input. + */ +export type FocusArea = "files" | "diff" | "filter"; diff --git a/src/ui/lib/reviewState.ts b/src/ui/lib/reviewState.ts index c5a647ba..aad0ecac 100644 --- a/src/ui/lib/reviewState.ts +++ b/src/ui/lib/reviewState.ts @@ -17,6 +17,7 @@ import { buildSidebarEntries, filterReviewFiles, mergeFileAnnotationsByFileId, + stepFileInList, type SidebarEntry, } from "./files"; import { @@ -106,14 +107,7 @@ export function findNextAnnotatedFile( delta: number, ) { const annotatedFiles = visibleFiles.filter((file) => file.agent); - if (annotatedFiles.length === 0) { - return null; - } - - const currentIndex = annotatedFiles.findIndex((file) => file.id === currentFileId); - const normalizedIndex = currentIndex >= 0 ? currentIndex : 0; - const nextIndex = (normalizedIndex + delta + annotatedFiles.length) % annotatedFiles.length; - return annotatedFiles[nextIndex] ?? null; + return stepFileInList(annotatedFiles, currentFileId, delta, "wrap"); } /** Resolve one session-daemon navigation request against the review stream's current state. */ diff --git a/src/ui/lib/ui-lib.test.ts b/src/ui/lib/ui-lib.test.ts index 98c77b8f..1c9f176e 100644 --- a/src/ui/lib/ui-lib.test.ts +++ b/src/ui/lib/ui-lib.test.ts @@ -151,10 +151,10 @@ describe("ui helpers", () => { menus.file .filter((entry): entry is Extract => entry.kind === "item") .map((entry) => entry.label), - ).toEqual(["Toggle files/filter focus", "Focus filter", "Reload", "Quit"]); + ).toEqual(["Toggle files/diff focus", "Focus filter", "Reload", "Quit"]); expect(menus.file[0]).toMatchObject({ kind: "item", - label: "Toggle files/filter focus", + label: "Toggle files/diff focus", hint: "Tab", }); expect( diff --git a/test/pty/ui-integration.test.ts b/test/pty/ui-integration.test.ts index cd769913..1316a14b 100644 --- a/test/pty/ui-integration.test.ts +++ b/test/pty/ui-integration.test.ts @@ -506,7 +506,7 @@ describe("live UI integration", () => { expect(initial).toContain("add = true"); expect(initial).toContain("betaValue"); - await session.press("tab"); + await session.type("/"); await session.type("beta"); const filtered = await harness.waitForSnapshot( session, @@ -917,7 +917,7 @@ describe("live UI integration", () => { await session.press("f10"); const fileMenu = await harness.waitForSnapshot( session, - (text) => text.includes("Toggle files/filter focus") && text.includes("Quit"), + (text) => text.includes("Toggle files/diff focus") && text.includes("Quit"), 5_000, );