diff --git a/.changeset/sunny-ads-hang.md b/.changeset/sunny-ads-hang.md new file mode 100644 index 000000000..596dcb4a2 --- /dev/null +++ b/.changeset/sunny-ads-hang.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": minor +--- + +Add configuration and CLI flags to control the sidebar in non-pager mode. diff --git a/README.md b/README.md index ddaf6c024..69e52bcd7 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,10 @@ vcs = "git" # git, jj, sl watch = false exclude_untracked = false line_numbers = true -tab_width = 4 # tab stops, 1-16 +tab_width = 4 # tab stops, 1-16 wrap_lines = false menu_bar = true +sidebar = "auto" # "auto", true, false agent_notes = false prompt_save_view_preferences = true transparent_background = false diff --git a/src/core/cli.test.ts b/src/core/cli.test.ts index 0ac924770..3bd7aeeac 100644 --- a/src/core/cli.test.ts +++ b/src/core/cli.test.ts @@ -191,6 +191,16 @@ describe("parseCli", () => { }); }); + test("parses sidebar toggles", async () => { + const shown = await parseCli(["bun", "hunk", "diff", "--sidebar"]); + const hidden = await parseCli(["bun", "hunk", "diff", "--no-sidebar"]); + const unset = await parseCli(["bun", "hunk", "diff"]); + + expect(shown).toMatchObject({ kind: "vcs", options: { sidebar: true } }); + expect(hidden).toMatchObject({ kind: "vcs", options: { sidebar: false } }); + expect(unset.kind === "vcs" ? unset.options.sidebar : "unset").toBeUndefined(); + }); + test("parses staged git-style diff aliases", async () => { const staged = await parseCli(["bun", "hunk", "diff", "--staged"]); const cached = await parseCli(["bun", "hunk", "diff", "--cached"]); diff --git a/src/core/cli.ts b/src/core/cli.ts index f95b66d09..59fb900f7 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -81,6 +81,8 @@ export const COMMON_REVIEW_OPTIONS = [ { flag: "--no-wrap", description: "truncate long diff lines to one row" }, { flag: "--hunk-headers", description: "show hunk metadata rows" }, { flag: "--no-hunk-headers", description: "hide hunk metadata rows" }, + { flag: "--sidebar", description: "show sidebar" }, + { flag: "--no-sidebar", description: "hide sidebar" }, { flag: "--agent-notes", description: "show agent notes by default" }, { flag: "--no-agent-notes", description: "hide agent notes by default" }, { flag: "--transparent-bg", description: "let terminal background show through Hunk surfaces" }, @@ -283,6 +285,7 @@ function buildCommonOptions( tabWidth: options.tabWidth, wrapLines: resolveBooleanFlag(argv, "--wrap", "--no-wrap"), hunkHeaders: resolveBooleanFlag(argv, "--hunk-headers", "--no-hunk-headers"), + sidebar: resolveBooleanFlag(argv, "--sidebar", "--no-sidebar"), agentNotes: resolveBooleanFlag(argv, "--agent-notes", "--no-agent-notes"), transparentBackground: resolveBooleanFlag(argv, "--transparent-bg", "--no-transparent-bg"), // Read straight from argv so the absence of the flag stays undefined rather than @@ -394,6 +397,7 @@ function renderCliHelp() { " -x, --tab-width tab stop width: 1-16 (default: 4)", " --wrap / --no-wrap wrap or truncate long diff lines", " --hunk-headers / --no-hunk-headers show or hide hunk metadata rows", + " --sidebar / --no-sidebar show or hide sidebar by default", " --agent-notes / --no-agent-notes show or hide agent notes by default", " --transparent-bg / --no-transparent-bg let terminal background show through Hunk surfaces", " --theme named theme override", diff --git a/src/core/config.test.ts b/src/core/config.test.ts index 2dc1f6a2d..c89277e54 100644 --- a/src/core/config.test.ts +++ b/src/core/config.test.ts @@ -303,6 +303,27 @@ describe("config resolution", () => { } }); + test("resolves the sidebar preference from config, CLI flags, and the auto default", () => { + const home = createTempDir("hunk-config-home-"); + const repo = createTempDir("hunk-config-repo-"); + createRepo(repo); + + const resolveSidebar = (input: CliInput) => + resolveConfiguredCliInput(input, { cwd: repo, env: { HOME: home } }).input.options.sidebar; + + expect(resolveSidebar(createPatchPagerInput())).toBe("auto"); + + mkdirSync(join(home, ".config", "hunk"), { recursive: true }); + writeFileSync(join(home, ".config", "hunk", "config.toml"), "sidebar = false\n"); + expect(resolveSidebar(createPatchPagerInput())).toBe(false); + // `--sidebar` outranks the config layer. + expect(resolveSidebar(createPatchPagerInput({ sidebar: true }))).toBe(true); + + // Values outside `true`, `false`, and "auto" fall back to the built-in default. + writeFileSync(join(home, ".config", "hunk", "config.toml"), 'sidebar = "always"\n'); + expect(resolveSidebar(createPatchPagerInput())).toBe("auto"); + }); + test("merges custom theme overrides from global and repo config", () => { const home = createTempDir("hunk-config-home-"); const repo = createTempDir("hunk-config-repo-"); @@ -999,6 +1020,7 @@ describe("config resolution", () => { "tab_width = 8", "wrap_lines = true", "menu_bar = false", + "sidebar = true", "hunk_headers = false", "agent_notes = true", "copy_decorations = false", @@ -1027,6 +1049,7 @@ describe("config resolution", () => { expect(bootstrap.initialTabWidth).toBe(8); expect(bootstrap.initialWrapLines).toBe(true); expect(bootstrap.initialShowMenuBar).toBe(false); + expect(bootstrap.initialSidebar).toBe(true); expect(bootstrap.initialShowHunkHeaders).toBe(false); expect(bootstrap.initialShowAgentNotes).toBe(true); expect(bootstrap.initialCopyDecorations).toBe(false); diff --git a/src/core/config.ts b/src/core/config.ts index 2f09f31ee..355db2e6e 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -27,6 +27,7 @@ import type { LayoutMode, NamedCustomThemeConfig, PersistedViewPreferences, + SidebarVisibility, UserKeyBinding, VcsMode, } from "./types"; @@ -167,6 +168,11 @@ function normalizeVcsMode(value: unknown): VcsMode | undefined { return typeof value === "string" && value.trim().length > 0 ? value : undefined; } +/** Accept a plain boolean, or `auto` for responsive behavior. */ +function normalizeSidebarVisibility(value: unknown): SidebarVisibility | undefined { + return typeof value === "boolean" || value === "auto" ? value : undefined; +} + /** Accept only plain booleans from config files. */ function normalizeBoolean(value: unknown) { return typeof value === "boolean" ? value : undefined; @@ -301,6 +307,15 @@ export const CONFIG_REFERENCE_OPTIONS: readonly ConfigReferenceOption[] = [ runtimeDefault: DEFAULT_VIEW_PREFERENCES.showMenuBar, description: "Show the top application menu bar.", }, + { + key: "sidebar", + property: "sidebar", + type: "string or boolean", + accepted: '`"auto"`, `true`, or `false`', + runtimeDefault: "auto", + description: + "Show the sidebar if it fits, keep it closed, or let the responsive layout decide. Pager sessions always open with the sidebar closed.", + }, { key: "agent_notes", property: "agentNotes", @@ -827,6 +842,8 @@ function normalizeConfigReferenceValue(property: keyof CommonOptions, value: unk return normalizeString(value); case "tabWidth": return normalizeTabWidth(value); + case "sidebar": + return normalizeSidebarVisibility(value); default: return normalizeBoolean(value); } @@ -885,6 +902,7 @@ function mergeOptions(base: CommonOptions, overrides: CommonOptions): CommonOpti wrapLines: overrides.wrapLines ?? base.wrapLines, hunkHeaders: overrides.hunkHeaders ?? base.hunkHeaders, menuBar: overrides.menuBar ?? base.menuBar, + sidebar: overrides.sidebar ?? base.sidebar, agentNotes: overrides.agentNotes ?? base.agentNotes, copyDecorations: overrides.copyDecorations ?? base.copyDecorations, promptSaveViewPreferences: @@ -1101,6 +1119,7 @@ export function resolveConfiguredCliInput( wrapLines: resolvedOptions.wrapLines ?? DEFAULT_VIEW_PREFERENCES.wrapLines, hunkHeaders: resolvedOptions.hunkHeaders ?? DEFAULT_VIEW_PREFERENCES.showHunkHeaders, menuBar: resolvedOptions.menuBar ?? DEFAULT_VIEW_PREFERENCES.showMenuBar, + sidebar: resolvedOptions.sidebar ?? "auto", agentNotes: resolvedOptions.agentNotes ?? DEFAULT_VIEW_PREFERENCES.showAgentNotes, copyDecorations: resolvedOptions.copyDecorations ?? DEFAULT_VIEW_PREFERENCES.copyDecorations, promptSaveViewPreferences: resolvedOptions.promptSaveViewPreferences ?? true, diff --git a/src/core/loaders.ts b/src/core/loaders.ts index e630939bc..3368e94b0 100644 --- a/src/core/loaders.ts +++ b/src/core/loaders.ts @@ -511,6 +511,7 @@ export async function loadAppBootstrap( initialWrapLines: input.options.wrapLines ?? false, initialShowHunkHeaders: input.options.hunkHeaders ?? true, initialShowMenuBar: input.options.menuBar ?? true, + initialSidebar: input.options.sidebar ?? "auto", initialShowAgentNotes: input.options.agentNotes ?? false, initialCopyDecorations: input.options.copyDecorations ?? false, initialCursorLine: input.options.cursorLine ?? "row", diff --git a/src/core/types.ts b/src/core/types.ts index 8ebcd95de..6520b3eea 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -30,6 +30,7 @@ export type { export type LayoutMode = "auto" | "split" | "stack"; export type CursorLine = "row" | "number" | "off"; +export type SidebarVisibility = boolean | "auto"; export type VcsMode = string; export type TerminalThemeMode = "light" | "dark"; @@ -101,6 +102,7 @@ export interface CommonOptions { wrapLines?: boolean; hunkHeaders?: boolean; menuBar?: boolean; + sidebar?: SidebarVisibility; agentNotes?: boolean; copyDecorations?: boolean; promptSaveViewPreferences?: boolean; @@ -391,6 +393,7 @@ export interface AppBootstrap { initialWrapLines?: boolean; initialShowHunkHeaders?: boolean; initialShowMenuBar?: boolean; + initialSidebar?: SidebarVisibility; initialShowAgentNotes?: boolean; initialCopyDecorations?: boolean; initialCursorLine?: CursorLine; diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 1b6947f63..a8ebb27b8 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -18,6 +18,7 @@ import type { CursorLine, LayoutMode, PersistedViewPreferences, + SidebarVisibility, UserNoteLineTarget, } from "../core/types"; import { canReloadInput } from "../core/watch"; @@ -223,8 +224,9 @@ export function App({ selectedIndex: 0, previewThemeId: null, }); - const [sidebarVisible, setSidebarVisible] = useState(() => !pagerMode); - const [forceSidebarOpen, setForceSidebarOpen] = useState(false); + const [sidebarState, setSidebarState] = useState(() => + pagerMode ? false : (bootstrap.initialSidebar ?? "auto"), + ); const [showHelp, setShowHelp] = useState(false); const [showAgentSkill, setShowAgentSkill] = useState(false); const [saveConfigPromptOpen, setSaveConfigPromptOpen] = useState(false); @@ -372,8 +374,8 @@ export function App({ // callbacks are assigned there each render and only ever read at command // invocation, keeping the dispatch table free of their identities. const extensionCommandNavigationRef = useRef({ - onSelectFile: (_fileId: string) => {}, - onSelectHunk: (_fileId: string, _hunkIndex: number) => {}, + onSelectFile: (_fileId: string) => { }, + onSelectHunk: (_fileId: string, _hunkIndex: number) => { }, }); // A hard session reload (`resetApp`) remounts App under an in-flight async // command handler, whose `ctx.navigation` closes over *this* instance's @@ -563,7 +565,7 @@ export function App({ * Reveal the sidebar area, assigned each render once the responsive layout * is known (the controls above are created before it is computed). */ - const revealSidebarAreaRef = useRef<() => void>(() => {}); + const revealSidebarAreaRef = useRef<() => void>(() => { }); const { accept: acceptExtensionDialog, @@ -598,7 +600,7 @@ export function App({ const report = (error: unknown) => { extensions?.context.notify( `Extension ${registered.extensionId} failed command "${registered.command.id}" • ` + - `${error instanceof Error ? error.message || error.name : String(error)}`, + `${error instanceof Error ? error.message || error.name : String(error)}`, "warning", ); }; @@ -705,7 +707,7 @@ export function App({ reportedCommandConflictsRef.current.add(reportKey); extensions?.context.notify( `Extension ${conflict.extensionId} key "${conflict.key}" is taken by ${conflict.conflictingId} • ` + - `command "${conflict.fullId}" left unbound`, + `command "${conflict.fullId}" left unbound`, "warning", ); } @@ -770,7 +772,9 @@ export function App({ const responsiveLayout = resolveResponsiveLayout(layoutMode, terminal.width); const canForceShowSidebar = bodyWidth >= SIDEBAR_MIN_WIDTH + DIVIDER_WIDTH + DIFF_MIN_WIDTH; const sidebarAreaVisible = - sidebarVisible && (responsiveLayout.showSidebar || (forceSidebarOpen && canForceShowSidebar)); + sidebarState === "auto" ? responsiveLayout.showSidebar : sidebarState && canForceShowSidebar; + const openSidebarState: SidebarVisibility = + !responsiveLayout.showSidebar && canForceShowSidebar ? true : "auto"; const resolvedLayout = responsiveLayout.layout; const reportedLayoutRef = useRef(undefined); useEffect(() => { @@ -787,15 +791,15 @@ export function App({ () => sidebarAreaVisible ? planSidebarLayout({ - views: sessionSidebarViews, - openKeys: sidebarOpenState.open, - widths: sidebarWidths, - defaultWidth: SIDEBAR_DEFAULT_WIDTH, - minWidth: SIDEBAR_MIN_WIDTH, - dividerWidth: DIVIDER_WIDTH, - bodyWidth, - diffMinWidth: DIFF_MIN_WIDTH, - }) + views: sessionSidebarViews, + openKeys: sidebarOpenState.open, + widths: sidebarWidths, + defaultWidth: SIDEBAR_DEFAULT_WIDTH, + minWidth: SIDEBAR_MIN_WIDTH, + dividerWidth: DIVIDER_WIDTH, + bodyWidth, + diffMinWidth: DIFF_MIN_WIDTH, + }) : { left: [], right: [], totalWidth: 0, leftWidth: 0 }, [ bodyWidth, @@ -817,9 +821,8 @@ export function App({ // Mirrors toggleSidebar's reveal half: visible again, forced open when the // responsive layout alone would keep it hidden and the terminal has room. revealSidebarAreaRef.current = () => { - setSidebarVisible(true); - if (!responsiveLayout.showSidebar && canForceShowSidebar) { - setForceSidebarOpen(true); + if (!sidebarAreaVisible) { + setSidebarState(openSidebarState); } }; // Publish the live note geometry for daemon-driven markup validation; the @@ -1093,21 +1096,7 @@ export function App({ /** Toggle the sidebar, forcing it open on narrower layouts when the app can still fit both panes. */ const toggleSidebar = () => { - if (sidebarVisible && (responsiveLayout.showSidebar || forceSidebarOpen)) { - setSidebarVisible(false); - setForceSidebarOpen(false); - return; - } - - if (sidebarVisible && !responsiveLayout.showSidebar) { - if (canForceShowSidebar) { - setForceSidebarOpen(true); - } - return; - } - - setSidebarVisible(true); - setForceSidebarOpen(!responsiveLayout.showSidebar && canForceShowSidebar); + setSidebarState(sidebarAreaVisible ? false : openSidebarState); }; /** Toggle visibility of hunk metadata rows without changing the actual diff lines. */ @@ -1145,8 +1134,8 @@ export function App({ resetApp: false, sourcePath: bootstrap.input.kind === "vcs" || - bootstrap.input.kind === "show" || - bootstrap.input.kind === "stash-show" + bootstrap.input.kind === "show" || + bootstrap.input.kind === "stash-show" ? bootstrap.changeset.sourceLabel : undefined, }); @@ -1264,8 +1253,8 @@ export function App({ const triggerEditSelectedFile = useCallback(() => { const basePath = bootstrap.input.kind === "vcs" || - bootstrap.input.kind === "show" || - bootstrap.input.kind === "stash-show" + bootstrap.input.kind === "show" || + bootstrap.input.kind === "stash-show" ? bootstrap.changeset.sourceLabel : undefined; const message = openSelectedFileInEditor({ @@ -1911,8 +1900,8 @@ export function App({ ) : null} {focusArea === "filter" || - Boolean(review.filter) || - Boolean(sessionNoticeText ?? transientNoticeText ?? noticeText) ? ( + Boolean(review.filter) || + Boolean(sessionNoticeText ?? transientNoticeText ?? noticeText) ? ( >) { + await act(async () => { + await setup.renderOnce(); + await Bun.sleep(0); + await setup.renderOnce(); + }); +} + +/** Whether the sidebar/diff divider sits at its default column on the probe row. */ +function sidebarVisible(setup: Awaited>) { + const row = setup.captureCharFrame().split("\n")[PROBE_ROW] ?? ""; + return row.indexOf("│") === SIDEBAR_DIVIDER_COLUMN; +} + +let setup: Awaited> | null = null; + +beforeEach(() => { + setup = null; +}); + +afterEach(() => { + setup?.renderer.destroy(); + setup = null; +}); + +describe("AppHost sidebar visibility preference", () => { + test("auto shows the sidebar on a full-width viewport", async () => { + setup = await testRender(, WIDE); + await flush(setup); + + expect(sidebarVisible(setup)).toBe(true); + }); + + test("auto hides the sidebar below the full-width viewport", async () => { + setup = await testRender(, MEDIUM); + await flush(setup); + + expect(sidebarVisible(setup)).toBe(false); + }); + + test("the toggle forces the sidebar open where auto hides it", async () => { + setup = await testRender(, MEDIUM); + await flush(setup); + expect(sidebarVisible(setup)).toBe(false); + + await act(async () => { + setup!.mockInput.pressKey("s"); + }); + await flush(setup); + expect(sidebarVisible(setup)).toBe(true); + + // A second press closes it again rather than returning to the responsive default. + await act(async () => { + setup!.mockInput.pressKey("s"); + }); + await flush(setup); + expect(sidebarVisible(setup)).toBe(false); + }); + + test("true shows the sidebar where auto would hide it", async () => { + setup = await testRender(, MEDIUM); + await flush(setup); + + expect(sidebarVisible(setup)).toBe(true); + }); + + test("false starts the sidebar closed but leaves the toggle working", async () => { + setup = await testRender(, WIDE); + await flush(setup); + expect(sidebarVisible(setup)).toBe(false); + + await act(async () => { + setup!.mockInput.pressKey("s"); + }); + await flush(setup); + + expect(sidebarVisible(setup)).toBe(true); + }); +}); diff --git a/test/pty/layout.test.ts b/test/pty/layout.test.ts index 48e43c97d..3037e81e5 100644 --- a/test/pty/layout.test.ts +++ b/test/pty/layout.test.ts @@ -263,6 +263,55 @@ describe("PTY layout", () => { } }); + test("--sidebar shows the sidebar below the viewport width that would reveal it", async () => { + const fixture = harness.createTwoFileRepoFixture(); + const session = await harness.launchHunk({ + args: ["diff", "--mode", "split", "--sidebar"], + cwd: fixture.dir, + cols: 180, + rows: 18, + }); + + try { + const frame = await session.waitForText(/View\s+Navigate\s+Agent\s+Help/, { + timeout: 15_000, + }); + + expect(harness.countMatches(frame, /alpha\.ts/g)).toBeGreaterThanOrEqual(2); + } finally { + session.close(); + } + }); + + test("--no-sidebar opens the review with the sidebar closed", async () => { + const fixture = harness.createTwoFileRepoFixture(); + const session = await harness.launchHunk({ + args: ["diff", "--mode", "split", "--no-sidebar"], + cwd: fixture.dir, + cols: 220, + rows: 18, + }); + + try { + const frame = await session.waitForText(/View\s+Navigate\s+Agent\s+Help/, { + timeout: 15_000, + }); + + expect(harness.countMatches(frame, /alpha\.ts/g)).toBe(1); + + await session.type("s"); + const toggled = await harness.waitForSnapshot( + session, + (text) => harness.countMatches(text, /alpha\.ts/g) >= 2, + 5_000, + ); + + expect(harness.countMatches(toggled, /alpha\.ts/g)).toBeGreaterThanOrEqual(2); + } finally { + session.close(); + } + }); + test("dragging the sidebar divider resizes the review pane in a real PTY", async () => { const fixture = harness.createTwoFileRepoFixture(); const session = await harness.launchHunk({ diff --git a/test/pty/pager.test.ts b/test/pty/pager.test.ts index 95348daa6..553530dcb 100644 --- a/test/pty/pager.test.ts +++ b/test/pty/pager.test.ts @@ -396,6 +396,35 @@ describe("PTY pager", () => { } }); + test("pager mode opens with the sidebar closed even when --sidebar asks for one", async () => { + const fixture = harness.createPagerPatchFixture(); + const session = await harness.launchHunkWithFileBackedStdin({ + stdinFile: fixture.patchFile, + args: ["pager", "--sidebar"], + cols: 120, + rows: 14, + }); + + try { + const initial = await session.waitForText(/scroll\.ts/, { timeout: 15_000 }); + + expect(harness.countMatches(initial, /scroll\.ts/g)).toBe(1); + + await session.waitIdle({ timeout: 200 }); + await session.press("s"); + const sidebarRow = /\bM scroll\.ts\s+\+40 -40/; + const withSidebar = await harness.waitForSnapshot( + session, + (text) => sidebarRow.test(text), + 5_000, + ); + + expect(withSidebar).toMatch(sidebarRow); + } finally { + session.close(); + } + }); + test("explicit pager mode still supports mouse wheel scrolling on a TTY", async () => { const fixture = harness.createPagerPatchFixture(60); const session = await harness.launchHunk({ diff --git a/website/src/content/docs/docs/configure/configuration.md b/website/src/content/docs/docs/configure/configuration.md index 9fdd1bf78..2a64199a5 100644 --- a/website/src/content/docs/docs/configure/configuration.md +++ b/website/src/content/docs/docs/configure/configuration.md @@ -23,6 +23,7 @@ tab_width = 4 wrap_lines = false hunk_headers = true menu_bar = true +sidebar = "auto" agent_notes = false transparent_background = false ``` diff --git a/website/src/content/docs/docs/configure/layout-and-display.md b/website/src/content/docs/docs/configure/layout-and-display.md index b71636b55..dc7c4dc81 100644 --- a/website/src/content/docs/docs/configure/layout-and-display.md +++ b/website/src/content/docs/docs/configure/layout-and-display.md @@ -37,6 +37,7 @@ line_numbers = true wrap_lines = false hunk_headers = true menu_bar = true +sidebar = "auto" agent_notes = false copy_decorations = false transparent_background = false diff --git a/website/src/content/docs/docs/reference/cli.md b/website/src/content/docs/docs/reference/cli.md index 4e12c77c9..2ca680850 100644 --- a/website/src/content/docs/docs/reference/cli.md +++ b/website/src/content/docs/docs/reference/cli.md @@ -31,6 +31,8 @@ This reference is generated from the command metadata used by Hunk itself. Run ` | `--no-wrap` | truncate long diff lines to one row | | `--hunk-headers` | show hunk metadata rows | | `--no-hunk-headers` | hide hunk metadata rows | +| `--sidebar` | show sidebar | +| `--no-sidebar` | hide sidebar | | `--agent-notes` | show agent notes by default | | `--no-agent-notes` | hide agent notes by default | | `--transparent-bg` | let terminal background show through Hunk surfaces | diff --git a/website/src/content/docs/docs/reference/config.md b/website/src/content/docs/docs/reference/config.md index a88000629..956cf4b03 100644 --- a/website/src/content/docs/docs/reference/config.md +++ b/website/src/content/docs/docs/reference/config.md @@ -130,6 +130,16 @@ Show the top application menu bar. --- +**`sidebar`** + +Show the sidebar if it fits, keep it closed, or let the responsive layout decide. Pager sessions always open with the sidebar closed. + +- **Type:** string or boolean +- **Accepted:** `"auto"`, `true`, or `false` +- **Built-in default:** `auto` + +--- + **`agent_notes`** Show agent notes when a review opens.