diff --git a/benchmarks/highlight-prefetch.ts b/benchmarks/highlight-prefetch.ts index bd73b20d..88f6d575 100644 --- a/benchmarks/highlight-prefetch.ts +++ b/benchmarks/highlight-prefetch.ts @@ -5,7 +5,7 @@ import React from "react"; import { testRender } from "@opentui/react/test-utils"; import { parseDiffFromFile } from "@pierre/diffs"; import { act } from "react"; -import { App } from "../src/ui/App"; +import { AppHost } from "../src/ui/App"; import type { AppBootstrap, DiffFile } from "../src/core/types"; function createDiffFile(index: number, marker: string): DiffFile { @@ -94,7 +94,7 @@ function frameHasHighlightedMarker( }); } -const setup = await testRender(React.createElement(App, { bootstrap: createBootstrap() }), { +const setup = await testRender(React.createElement(AppHost, { bootstrap: createBootstrap() }), { width: 240, height: 24, }); diff --git a/benchmarks/large-stream.ts b/benchmarks/large-stream.ts index 1d0c5d28..36bed1b2 100644 --- a/benchmarks/large-stream.ts +++ b/benchmarks/large-stream.ts @@ -4,7 +4,7 @@ import { performance } from "perf_hooks"; import React from "react"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import { App } from "../src/ui/App"; +import { AppHost } from "../src/ui/App"; import { createLargeSplitStreamBootstrap, DEFAULT_FILE_COUNT, @@ -69,7 +69,7 @@ async function destroyRenderer(setup: BenchmarkRenderer) { async function measureFirstFrameMs(notesPerFile: number) { const setup = await testRender( - React.createElement(App, { + React.createElement(AppHost, { bootstrap: createLargeSplitStreamBootstrap({ notesPerFile }), }), VIEWPORT, @@ -87,7 +87,7 @@ async function measureFirstFrameMs(notesPerFile: number) { async function measureScrollTicksMs(notesPerFile: number) { const setup = await testRender( - React.createElement(App, { + React.createElement(AppHost, { bootstrap: createLargeSplitStreamBootstrap({ notesPerFile }), }), VIEWPORT, diff --git a/src/main.tsx b/src/main.tsx index 09199159..61fda95b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -7,7 +7,7 @@ import { pagePlainText } from "./core/pager"; import { shutdownSession } from "./core/shutdown"; import { prepareStartupPlan } from "./core/startup"; import { resolveStartupUpdateNotice } from "./core/updateNotice"; -import { App } from "./ui/App"; +import { AppHost } from "./ui/App"; import { HunkHostClient } from "./mcp/client"; import { serveHunkMcpServer } from "./mcp/server"; import { createInitialSessionSnapshot, createSessionRegistration } from "./mcp/sessionRegistration"; @@ -74,7 +74,7 @@ async function main() { // The app owns the full alternate screen session from this point on. root.render( - void; onReloadSession: ( nextInput: CliInput, - options?: { resetShell?: boolean; sourcePath?: string }, + options?: { resetApp?: boolean; sourcePath?: string }, ) => Promise; }) { const FILES_MIN_WIDTH = 22; @@ -227,7 +227,7 @@ function AppShell({ }, [maxFilesPaneWidth, showFilesPane]); useEffect(() => { - // Force an intermediate redraw when shell geometry or row-wrapping changes so pane relayout + // Force an intermediate redraw when app geometry or row-wrapping changes so pane relayout // feels immediate after toggling split/stack or line wrapping. renderer.intermediateRender(); }, [renderer, resolvedLayout, showFilesPane, terminal.height, terminal.width, wrapLines]); @@ -353,7 +353,7 @@ function AppShell({ setWrapLines((current) => !current); }; - /** Toggle the sidebar, forcing it open on narrower layouts when the shell can still fit both panes. */ + /** Toggle the sidebar, forcing it open on narrower layouts when the app can still fit both panes. */ const toggleSidebar = () => { if (sidebarVisible && (responsiveLayout.showFilesPane || forceSidebarOpen)) { setSidebarVisible(false); @@ -385,7 +385,7 @@ function AppShell({ const canRefreshCurrentInput = canReloadInput(bootstrap.input); const watchEnabled = Boolean(bootstrap.input.options.watch && canRefreshCurrentInput); - /** Rebuild the current diff source while preserving the active shell view options. */ + /** Rebuild the current diff source while preserving the active app view options. */ const refreshCurrentInput = useCallback(async () => { if (!canRefreshCurrentInput) { return; @@ -400,7 +400,7 @@ function AppShell({ wrapLines, }); - await onReloadSession(nextInput, { resetShell: false }); + await onReloadSession(nextInput, { resetApp: false }); }, [ bootstrap.input, canRefreshCurrentInput, @@ -470,7 +470,7 @@ function AppShell({ }; }, [bootstrap.input, refreshCurrentInput, watchEnabled]); - /** Leave the app through the shell-owned shutdown path. */ + /** Leave the app through the shared shutdown path. */ const requestQuit = useCallback(() => { onQuit(); }, [onQuit]); @@ -1045,8 +1045,8 @@ function AppShell({ ); } -/** Keep one live Hunk window mounted while allowing daemon-driven session reloads. */ -export function App({ +/** Keep one live Hunk app mounted while allowing daemon-driven session reloads. */ +export function AppHost({ bootstrap, hostClient, onQuit = () => process.exit(0), @@ -1058,14 +1058,14 @@ export function App({ startupNoticeResolver?: () => Promise; }) { const [activeBootstrap, setActiveBootstrap] = useState(bootstrap); - const [shellVersion, setShellVersion] = useState(0); + const [appVersion, setAppVersion] = useState(0); const startupNoticeText = useStartupUpdateNotice({ enabled: !bootstrap.input.options.pager, resolver: startupNoticeResolver, }); const reloadSession = useCallback( - async (nextInput: CliInput, options?: { resetShell?: boolean; sourcePath?: string }) => { + async (nextInput: CliInput, options?: { resetApp?: boolean; sourcePath?: string }) => { const runtimeInput = resolveRuntimeCliInput(nextInput); const configuredInput = resolveConfiguredCliInput(runtimeInput, { cwd: options?.sourcePath, @@ -1086,8 +1086,8 @@ export function App({ } setActiveBootstrap(nextBootstrap); - if (options?.resetShell !== false) { - setShellVersion((current) => current + 1); + if (options?.resetApp !== false) { + setAppVersion((current) => current + 1); } return { @@ -1104,8 +1104,8 @@ export function App({ ); return ( - void; reloadSession: ( nextInput: CliInput, - options?: { resetShell?: boolean; sourcePath?: string }, + options?: { resetApp?: boolean; sourcePath?: string }, ) => Promise; selectedFile: DiffFile | undefined; selectedHunkIndex: number; diff --git a/src/ui/lib/appMenus.ts b/src/ui/lib/appMenus.ts index b38901cb..663de32f 100644 --- a/src/ui/lib/appMenus.ts +++ b/src/ui/lib/appMenus.ts @@ -29,7 +29,7 @@ export interface BuildAppMenusOptions { wrapLines: boolean; } -/** Build the top-level app menus from the current shell state and actions. */ +/** Build the top-level app menus from the current app state and actions. */ export function buildAppMenus({ activeThemeId, canRefreshCurrentInput, diff --git a/src/ui/lib/files.ts b/src/ui/lib/files.ts index cc08af32..d877d837 100644 --- a/src/ui/lib/files.ts +++ b/src/ui/lib/files.ts @@ -53,7 +53,7 @@ export function mergeFileAnnotationsByFileId( }); } -/** Apply the shell's file filter query to the visible review stream. */ +/** Apply the app's file filter query to the visible review stream. */ export function filterReviewFiles(files: DiffFile[], query: string): DiffFile[] { const trimmedQuery = query.trim().toLowerCase(); if (!trimmedQuery) { diff --git a/src/ui/lib/responsive.ts b/src/ui/lib/responsive.ts index 949c3ea3..c739f446 100644 --- a/src/ui/lib/responsive.ts +++ b/src/ui/lib/responsive.ts @@ -11,7 +11,7 @@ export interface ResponsiveLayout { showFilesPane: boolean; } -/** Bucket terminal widths into the viewport classes the shell layout cares about. */ +/** Bucket terminal widths into the viewport classes the app layout cares about. */ function resolveResponsiveViewport(viewportWidth: number): ResponsiveViewport { if (viewportWidth >= FULL_VIEWPORT_MIN_WIDTH) { return "full"; diff --git a/src/ui/lib/sidebar.ts b/src/ui/lib/sidebar.ts index 6820072b..2916152a 100644 --- a/src/ui/lib/sidebar.ts +++ b/src/ui/lib/sidebar.ts @@ -1,4 +1,4 @@ -/** Clamp a dragged sidebar width into the shell's allowed range. */ +/** Clamp a dragged sidebar width into the app layout's allowed range. */ export function resizeSidebarWidth( startWidth: number, dragOriginX: number, diff --git a/test/app-interactions.test.tsx b/test/app-interactions.test.tsx index ae5d3ae8..cb76fd4c 100644 --- a/test/app-interactions.test.tsx +++ b/test/app-interactions.test.tsx @@ -10,7 +10,7 @@ import type { HunkSessionRegistration, SessionServerMessage } from "../src/mcp/t import type { AppBootstrap, DiffFile, LayoutMode } from "../src/core/types"; const { loadAppBootstrap } = await import("../src/core/loaders"); -const { App } = await import("../src/ui/App"); +const { AppHost } = await import("../src/ui/App"); function createDiffFile( id: string, @@ -369,7 +369,7 @@ function firstVisibleAddedLine(frame: string) { describe("App interactions", () => { test("keyboard shortcuts toggle notes, line numbers, and hunk metadata", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 240, height: 24, }); @@ -418,8 +418,8 @@ describe("App interactions", () => { } }); - test("keyboard shortcut can wrap long lines in the app shell", async () => { - const setup = await testRender(, { + test("keyboard shortcut can wrap long lines in the app", async () => { + const setup = await testRender(, { width: 140, height: 20, }); @@ -447,7 +447,7 @@ describe("App interactions", () => { }); test("pager mode keyboard shortcut can wrap long lines", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 140, height: 20, }); @@ -475,7 +475,7 @@ describe("App interactions", () => { }); test("keyboard shortcut can toggle line wrapping on, off, and on again", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 102, height: 24, }); @@ -522,7 +522,7 @@ describe("App interactions", () => { test("bootstrap preferences initialize the visible view state", async () => { const setup = await testRender( - { }); test("menu navigation can switch layouts and activate view actions", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 24, }); @@ -642,7 +642,7 @@ describe("App interactions", () => { }, }); - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 20, }); @@ -697,7 +697,7 @@ describe("App interactions", () => { }, }); - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 20, }); @@ -744,7 +744,7 @@ describe("App interactions", () => { ], }; - const setup = await testRender(, { width: 240, height: 32 }); + const setup = await testRender(, { width: 240, height: 32 }); try { await flush(setup); @@ -767,7 +767,7 @@ describe("App interactions", () => { }); test("arrow keys scroll the review pane line by line", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 12, }); @@ -814,7 +814,7 @@ describe("App interactions", () => { }); test("pager mode arrow keys also scroll line by line", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 8, }); @@ -861,7 +861,7 @@ describe("App interactions", () => { }); test("toggling wrap preserves the current viewport anchor instead of snapping to the top", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 102, height: 12, }); @@ -952,7 +952,7 @@ describe("App interactions", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 12, }); @@ -1005,7 +1005,7 @@ describe("App interactions", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 12, }); @@ -1063,7 +1063,7 @@ describe("App interactions", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 12, }); @@ -1101,7 +1101,7 @@ describe("App interactions", () => { }); test("filter focus accepts typed input and narrows the visible file set", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 240, height: 24, }); @@ -1130,7 +1130,7 @@ describe("App interactions", () => { }); test("filtering away the selected file reselects the first visible match", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 240, height: 24, }); @@ -1170,10 +1170,13 @@ describe("App interactions", () => { test("CLI comment navigation respects the active file filter", async () => { const { hostClient, navigateToHunk } = createMockHostClient(); - const setup = await testRender(, { - width: 240, - height: 24, - }); + const setup = await testRender( + , + { + width: 240, + height: 24, + }, + ); try { await flush(setup); @@ -1221,7 +1224,7 @@ describe("App interactions", () => { test("CLI comment navigation scrolls the inline note into view", async () => { const { hostClient, navigateToHunk } = createMockHostClient(); const setup = await testRender( - , + , { width: 104, height: 18, @@ -1256,7 +1259,7 @@ describe("App interactions", () => { }); test("menu navigation wraps across the first and last top-level menus", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 220, height: 24, }); @@ -1298,7 +1301,7 @@ describe("App interactions", () => { }); test("sidebar visibility can toggle off and back on", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 240, height: 24, }); @@ -1332,7 +1335,7 @@ describe("App interactions", () => { }); test("sidebar shortcut can force the files pane open when responsive layout hides it", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 24, }); @@ -1368,7 +1371,7 @@ describe("App interactions", () => { test("quit shortcuts route through the provided onQuit handler in regular and pager modes", async () => { const regularQuit = mock(() => undefined); const regularSetup = await testRender( - , + , { width: 220, height: 24 }, ); @@ -1388,7 +1391,7 @@ describe("App interactions", () => { const pagerQuit = mock(() => undefined); const pagerSetup = await testRender( - , + , { width: 180, height: 20 }, ); diff --git a/test/app-responsive.test.tsx b/test/app-responsive.test.tsx index d8a70710..d124e174 100644 --- a/test/app-responsive.test.tsx +++ b/test/app-responsive.test.tsx @@ -4,7 +4,7 @@ import { parseDiffFromFile } from "@pierre/diffs"; import { act } from "react"; import type { AppBootstrap, DiffFile, LayoutMode } from "../src/core/types"; -const { App } = await import("../src/ui/App"); +const { AppHost } = await import("../src/ui/App"); function createDiffFile( id: string, @@ -98,7 +98,7 @@ function createBootstrap(initialMode: LayoutMode = "auto", pager = false): AppBo } async function captureFrameForBootstrap(bootstrap: AppBootstrap, width: number, height = 24) { - const setup = await testRender(, { width, height }); + const setup = await testRender(, { width, height }); try { await act(async () => { @@ -114,7 +114,10 @@ async function captureFrameForBootstrap(bootstrap: AppBootstrap, width: number, } async function captureResponsiveFrames() { - const setup = await testRender(, { width: 280, height: 24 }); + const setup = await testRender(, { + width: 280, + height: 24, + }); try { await act(async () => { @@ -148,7 +151,7 @@ async function captureResponsiveFrames() { } } -describe("responsive shell", () => { +describe("responsive app", () => { test("App adjusts the visible panes and diff layout on live resize", async () => { const { ultraWide, full, medium, tight } = await captureResponsiveFrames(); @@ -201,7 +204,7 @@ describe("responsive shell", () => { const exitMock = mock(() => undefined as never); (process as typeof process & { exit: typeof exitMock }).exit = exitMock; - const setup = await testRender(, { + const setup = await testRender(, { width: 240, height: 24, }); diff --git a/test/ui-components.test.tsx b/test/ui-components.test.tsx index a028d024..44d46d83 100644 --- a/test/ui-components.test.tsx +++ b/test/ui-components.test.tsx @@ -5,7 +5,7 @@ import { act, createRef, type ReactNode } from "react"; import type { AppBootstrap, DiffFile } from "../src/core/types"; import { resolveTheme } from "../src/ui/themes"; -const { App } = await import("../src/ui/App"); +const { AppHost } = await import("../src/ui/App"); const { buildSidebarEntries } = await import("../src/ui/lib/files"); const { HelpDialog } = await import("../src/ui/components/chrome/HelpDialog"); const { FilesPane } = await import("../src/ui/components/panes/FilesPane"); @@ -1446,7 +1446,7 @@ describe("UI components", () => { test("App renders the menu bar, multi-file stream, and AI badges", async () => { const bootstrap = createBootstrap(); - const frame = await captureFrame(, 280, 24); + const frame = await captureFrame(, 280, 24); expect(frame).toContain("File View Navigate Theme Agent Help"); expect(frame).toContain("alpha.ts"); diff --git a/test/ui-lib.test.ts b/test/ui-lib.test.ts index 95905ace..08b6fbe8 100644 --- a/test/ui-lib.test.ts +++ b/test/ui-lib.test.ts @@ -95,7 +95,7 @@ describe("ui helpers", () => { expect(menuBoxHeight(entries)).toBe(5); }); - test("buildAppMenus creates checked entries from the current shell state", () => { + test("buildAppMenus creates checked entries from the current app state", () => { const menus = buildAppMenus({ activeThemeId: "graphite", canRefreshCurrentInput: true, diff --git a/test/ui-scroll-regression.test.tsx b/test/ui-scroll-regression.test.tsx index 5f012f12..79ef471b 100644 --- a/test/ui-scroll-regression.test.tsx +++ b/test/ui-scroll-regression.test.tsx @@ -6,7 +6,7 @@ import type { AppBootstrap } from "../src/core/types"; mock.restore(); -const { App } = await import("../src/ui/App"); +const { AppHost } = await import("../src/ui/App"); function createScrollBootstrap(): AppBootstrap { const before = Array.from( @@ -68,7 +68,7 @@ function createScrollBootstrap(): AppBootstrap { describe("UI scroll regression", () => { test("keeps split diff lines intact after a wheel scroll repaint", async () => { - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 20, }); diff --git a/test/vertical-scrollbar.test.tsx b/test/vertical-scrollbar.test.tsx index 15b10fc9..0c42d588 100644 --- a/test/vertical-scrollbar.test.tsx +++ b/test/vertical-scrollbar.test.tsx @@ -4,7 +4,7 @@ import { parseDiffFromFile } from "@pierre/diffs"; import { act } from "react"; import type { AppBootstrap, DiffFile } from "../src/core/types"; -const { App } = await import("../src/ui/App"); +const { AppHost } = await import("../src/ui/App"); function createDiffFile(id: string, path: string, before: string, after: string): DiffFile { const metadata = parseDiffFromFile( @@ -85,7 +85,7 @@ async function flush(setup: Awaited>) { describe("Vertical scrollbar", () => { test("shows scrollbar when content exceeds viewport height", async () => { const bootstrap = createScrollBootstrapWithManyFiles(5); - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 20, }); @@ -120,7 +120,7 @@ describe("Vertical scrollbar", () => { test("hides scrollbar after scroll activity stops", async () => { const bootstrap = createScrollBootstrapWithManyFiles(5); - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 20, }); @@ -157,7 +157,7 @@ describe("Vertical scrollbar", () => { test("scrollbar shows on mouse scroll wheel activity", async () => { const bootstrap = createScrollBootstrapWithManyFiles(5); - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 20, }); @@ -212,7 +212,7 @@ describe("Vertical scrollbar", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 15, // Small viewport to force scrolling }); @@ -278,7 +278,7 @@ describe("Vertical scrollbar", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 60, // Large viewport }); @@ -321,7 +321,7 @@ describe("Vertical scrollbar", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 20, // Small viewport to force scrolling }); @@ -387,7 +387,7 @@ describe("Vertical scrollbar", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 15, // Viewport of 15 lines }); @@ -466,7 +466,7 @@ describe("Vertical scrollbar", () => { initialTheme: "midnight", }; - const setup = await testRender(, { + const setup = await testRender(, { width: 160, height: 15, // Small viewport to force scrolling (25 lines of content in 15-line viewport) });