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 a272103c5..39828751a 100644 --- a/README.md +++ b/README.md @@ -140,9 +140,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/changesetLoaders.ts b/src/core/changesetLoaders.ts index fde450bba..7d1ea27df 100644 --- a/src/core/changesetLoaders.ts +++ b/src/core/changesetLoaders.ts @@ -335,6 +335,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/cli.test.ts b/src/core/cli.test.ts index 5b7ad5ca2..6fe8b2e05 100644 --- a/src/core/cli.test.ts +++ b/src/core/cli.test.ts @@ -191,6 +191,45 @@ 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("keeps paired-flag-shaped pathspecs after the option separator", async () => { + const cases = [ + ["--exclude-untracked", "excludeUntracked"], + ["--no-exclude-untracked", "excludeUntracked"], + ["--line-numbers", "lineNumbers"], + ["--no-line-numbers", "lineNumbers"], + ["--wrap", "wrapLines"], + ["--no-wrap", "wrapLines"], + ["--hunk-headers", "hunkHeaders"], + ["--no-hunk-headers", "hunkHeaders"], + ["--sidebar", "sidebar"], + ["--no-sidebar", "sidebar"], + ["--agent-notes", "agentNotes"], + ["--no-agent-notes", "agentNotes"], + ["--transparent-bg", "transparentBackground"], + ["--no-transparent-bg", "transparentBackground"], + ["--extensions", "extensions"], + ["--no-extensions", "extensions"], + ] as const; + + for (const [pathspec, option] of cases) { + const parsed = await parseCli(["bun", "hunk", "diff", "--", pathspec]); + + expect(parsed).toMatchObject({ kind: "vcs", pathspecs: [pathspec] }); + if (parsed.kind !== "vcs") throw new Error("Expected a VCS diff input."); + expect(parsed.options[option]).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 c166eceff..ab16e1f73 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -91,6 +91,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 files pane" }, + { flag: "--no-sidebar", description: "hide files pane" }, { 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" }, @@ -282,11 +284,13 @@ function parseNonNegativeInt(value: string) { return parsed; } -/** Read one paired positive/negative boolean flag directly from raw argv. */ +/** Read one paired boolean flag before the pathspec separator in raw argv. */ function resolveBooleanFlag(argv: string[], enabledFlag: string, disabledFlag: string) { let resolved: boolean | undefined; for (const arg of argv) { + if (arg === "--") break; + if (arg === enabledFlag) { resolved = true; continue; @@ -341,6 +345,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 @@ -457,6 +462,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 files pane 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 2e659a637..e962b0b76 100644 --- a/src/core/config.test.ts +++ b/src/core/config.test.ts @@ -304,6 +304,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-"); @@ -994,6 +1015,7 @@ describe("config resolution", () => { "tab_width = 8", "wrap_lines = true", "menu_bar = false", + "sidebar = true", "hunk_headers = false", "agent_notes = true", "copy_decorations = false", @@ -1022,6 +1044,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 107acfa8e..892c88b74 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -32,6 +32,7 @@ import type { LayoutMode, NamedCustomThemeConfig, PersistedViewPreferences, + SidebarVisibility, UserKeyBinding, VcsMode, } from "./types"; @@ -179,6 +180,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; @@ -313,6 +319,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 files pane if it fits, keep it closed, or let the responsive layout decide. Pager sessions always open with the files pane closed.", + }, { key: "agent_notes", property: "agentNotes", @@ -839,6 +854,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); } @@ -897,6 +914,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: @@ -1117,6 +1135,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/types.ts b/src/core/types.ts index ba35a5c25..3e806ba46 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -27,6 +27,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"; @@ -99,6 +100,7 @@ export interface CommonOptions { wrapLines?: boolean; hunkHeaders?: boolean; menuBar?: boolean; + sidebar?: SidebarVisibility; agentNotes?: boolean; copyDecorations?: boolean; promptSaveViewPreferences?: boolean; @@ -445,6 +447,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 a5460a34a..23812bc21 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -331,7 +331,9 @@ export function App({ previewThemeId: null, }); const [sidebarVisible, setSidebarVisible] = useState(() => !pagerMode); - const [forceSidebarOpen, setForceSidebarOpen] = useState(false); + const [forceSidebarOpen, setForceSidebarOpen] = useState( + () => !pagerMode && bootstrap.initialSidebar === true, + ); const [showHelp, setShowHelp] = useState(false); const [showAgentSkill, setShowAgentSkill] = useState(false); const [saveConfigPromptOpen, setSaveConfigPromptOpen] = useState(false); @@ -351,7 +353,18 @@ export function App({ const sessionNoticeTimeoutRef = useRef | null>(null); const extensions = bootstrap.extensions as ExtensionLoadResult | undefined; const sessionPanes = useMemo(() => buildSessionPanes(extensions), [extensions]); - const [paneOpenState, setPaneOpenState] = useState(() => initialPaneOpenState(sessionPanes)); + const [paneOpenState, setPaneOpenState] = useState(() => { + const initial = initialPaneOpenState(sessionPanes); + if (bootstrap.initialSidebar !== false) return initial; + + // The preference targets the active files slot, not independently open extension panes. + const filesPaneKey = resolvePaneSlotKey({ + panes: sessionPanes, + slotKey: HUNK_FILES_PANE_KEY, + openKeys: initial.open, + }); + return { ...initial, open: initial.open.filter((key) => key !== filesPaneKey) }; + }); useEffect( () => setPaneOpenState((current) => reconcilePaneOpenState(sessionPanes, current)), [sessionPanes], diff --git a/src/ui/AppHost.sidebar-visibility.test.tsx b/src/ui/AppHost.sidebar-visibility.test.tsx new file mode 100644 index 000000000..a37ab88d8 --- /dev/null +++ b/src/ui/AppHost.sidebar-visibility.test.tsx @@ -0,0 +1,163 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { testRender } from "@opentui/react/test-utils"; +import { act } from "react"; +import type { AppBootstrap, SidebarVisibility } from "../core/types"; +import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; +import { createTestDiffFile as buildTestDiffFile, lines } from "../../test/helpers/diff-helpers"; +import { HUNK_FILES_PANE_KEY } from "../extensions/extensionIds"; +import { createEmptyExtensionLoadResult } from "../extensions/types"; + +const { AppHost } = await import("./AppHost"); + +/** Wide enough for the responsive layout to show the sidebar on its own. */ +const WIDE = { width: 240, height: 24 }; +/** Narrower than the full viewport, so `auto` hides the sidebar but both panes still fit. */ +const MEDIUM = { width: 180, height: 24 }; +// Default sidebar width (34) plus the body's 1-column left padding puts the divider at column 35. +const SIDEBAR_DIVIDER_COLUMN = 35; +// A stable mid-height row that always falls inside the sidebar/divider band. +const PROBE_ROW = 10; + +function createSidebarBootstrap(initialSidebar?: SidebarVisibility): AppBootstrap { + return { + ...createTestVcsAppBootstrap({ + changesetId: "changeset:sidebar-visibility", + initialMode: "split", + files: [ + buildTestDiffFile({ + after: lines("export const a = 10;"), + agent: false, + before: lines("export const a = 1;"), + context: 3, + id: "alpha", + path: "src/alpha.ts", + }), + ], + }), + initialSidebar, + }; +} + +/** Add an independent pane plus a replacement for the built-in files pane. */ +function createExtensionSidebarBootstrap(initialSidebar: SidebarVisibility): AppBootstrap { + const extensions = createEmptyExtensionLoadResult(); + extensions.registry.panes.push( + { + extensionId: "activity-test", + pane: { + id: "activity", + title: "Activity", + placement: "right", + defaultOpen: true, + component: () => , + }, + }, + { + extensionId: "replacement-test", + pane: { + id: "files", + title: "Replacement files", + replaces: HUNK_FILES_PANE_KEY, + component: () => , + }, + }, + ); + return { ...createSidebarBootstrap(initialSidebar), extensions }; +} + +/** Drive one or two render passes so pending state commits land before assertions. */ +async function flush(setup: Awaited>) { + 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); + }); + + test("false closes only the active files pane and preserves independent extension panes", async () => { + setup = await testRender(, WIDE); + await flush(setup); + + expect(setup.captureCharFrame()).toContain("ACTIVITY PANE"); + expect(setup.captureCharFrame()).not.toContain("REPLACEMENT FILES"); + + await act(async () => { + setup!.mockInput.pressKey("s"); + }); + await flush(setup); + + expect(setup.captureCharFrame()).toContain("ACTIVITY PANE"); + expect(setup.captureCharFrame()).toContain("REPLACEMENT FILES"); + }); +}); diff --git a/test/pty/layout.test.ts b/test/pty/layout.test.ts index baeb55130..69d36af42 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 5bdeb01fd..c45b394e5 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 files pane | +| `--no-sidebar` | hide files pane | | `--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..f099a5dad 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 files pane if it fits, keep it closed, or let the responsive layout decide. Pager sessions always open with the files pane closed. + +- **Type:** string or boolean +- **Accepted:** `"auto"`, `true`, or `false` +- **Built-in default:** `auto` + +--- + **`agent_notes`** Show agent notes when a review opens.