Skip to content

Commit f8dddc6

Browse files
authored
Preserve auto theme across watch refreshes (#318)
1 parent 80e6295 commit f8dddc6

3 files changed

Lines changed: 76 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ All notable user-visible changes to Hunk are documented in this file.
1212

1313
### Fixed
1414

15+
- Preserved the resolved auto theme across `--watch` refreshes instead of falling back to the default dark theme.
1516
- Included the bundled Hunk review skill in standalone prebuilt release archives so `hunk skill path` works after extracting a tarball or installing via Homebrew.
1617

1718
## [0.12.0] - 2026-05-12

src/ui/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export function App({
107107
? "auto"
108108
: resolveTheme(bootstrap.initialTheme, bootstrap.initialThemeMode ?? null).id,
109109
);
110+
// Soft reloads replace bootstrap without re-running startup terminal theme detection.
111+
const [detectedThemeMode] = useState(() => bootstrap.initialThemeMode);
110112
const [showAgentNotes, setShowAgentNotes] = useState(bootstrap.initialShowAgentNotes ?? false);
111113
const [showLineNumbers, setShowLineNumbers] = useState(bootstrap.initialShowLineNumbers ?? true);
112114
const [wrapLines, setWrapLines] = useState(bootstrap.initialWrapLines ?? false);
@@ -120,7 +122,7 @@ export function App({
120122
const [resizeDragOriginX, setResizeDragOriginX] = useState<number | null>(null);
121123
const [resizeStartWidth, setResizeStartWidth] = useState<number | null>(null);
122124

123-
const activeTheme = resolveTheme(themeId, bootstrap.initialThemeMode ?? null);
125+
const activeTheme = resolveTheme(themeId, detectedThemeMode ?? null);
124126
const review = useReviewController({ files: bootstrap.changeset.files });
125127
const filteredFiles = review.visibleFiles;
126128
const selectedFile = review.selectedFile;

src/ui/AppHost.interactions.test.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,29 @@ async function waitForFrame(
434434
return frame;
435435
}
436436

437+
/** Open the top-level Theme menu and wait for the expected active light theme marker. */
438+
async function openThemeMenu(setup: Awaited<ReturnType<typeof testRender>>) {
439+
await act(async () => {
440+
await setup.mockInput.pressKey("F10");
441+
});
442+
443+
const openedFrame = await waitForFrame(
444+
setup,
445+
(frame) => frame.includes("Toggle files/filter focus"),
446+
12,
447+
);
448+
expect(openedFrame).toContain("Toggle files/filter focus");
449+
450+
for (let index = 0; index < 3; index += 1) {
451+
await act(async () => {
452+
await setup.mockInput.pressArrow("right");
453+
});
454+
await flush(setup);
455+
}
456+
457+
return waitForFrame(setup, (frame) => frame.includes("[x] Paper"), 12);
458+
}
459+
437460
async function pressHunkNavigationKey(
438461
setup: Awaited<ReturnType<typeof testRender>>,
439462
key: "]" | "[",
@@ -1255,6 +1278,55 @@ describe("App interactions", () => {
12551278
}
12561279
});
12571280

1281+
test("watch mode preserves the resolved auto theme after refreshing the file diff", async () => {
1282+
const dir = mkdtempSync(join(tmpdir(), "hunk-watch-theme-"));
1283+
const left = join(dir, "before.ts");
1284+
const right = join(dir, "after.ts");
1285+
1286+
writeFileSync(left, "export const answer = 41;\n");
1287+
writeFileSync(right, "export const answer = 42;\n");
1288+
1289+
const bootstrap = await loadAppBootstrap({
1290+
kind: "diff",
1291+
left,
1292+
right,
1293+
options: {
1294+
mode: "split",
1295+
theme: "auto",
1296+
watch: true,
1297+
},
1298+
});
1299+
// loadAppBootstrap does not do startup-time terminal theme detection in tests.
1300+
bootstrap.initialThemeMode = "light";
1301+
1302+
const setup = await testRender(<AppHost bootstrap={bootstrap} />, {
1303+
width: 220,
1304+
height: 20,
1305+
});
1306+
1307+
try {
1308+
await flush(setup);
1309+
1310+
writeFileSync(right, "export const answer = 42;\nexport const added = true;\n");
1311+
1312+
const refreshedFrame = await waitForFrame(
1313+
setup,
1314+
(currentFrame) => currentFrame.includes("export const added = true;"),
1315+
40,
1316+
);
1317+
expect(refreshedFrame).toContain("export const added = true;");
1318+
1319+
const menuFrame = await openThemeMenu(setup);
1320+
expect(menuFrame).toContain("[x] Paper");
1321+
expect(menuFrame).toContain("[ ] Graphite");
1322+
} finally {
1323+
await act(async () => {
1324+
setup.renderer.destroy();
1325+
});
1326+
rmSync(dir, { force: true, recursive: true });
1327+
}
1328+
});
1329+
12581330
test("a shows notes that are visible in the current review viewport", async () => {
12591331
const bootstrap = createBootstrap();
12601332
bootstrap.changeset.files[1]!.agent = {

0 commit comments

Comments
 (0)