Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ All notable user-visible changes to Hunk are documented in this file.

### Fixed

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

## [0.12.0] - 2026-05-12
Expand Down
4 changes: 3 additions & 1 deletion src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export function App({
? "auto"
: resolveTheme(bootstrap.initialTheme, bootstrap.initialThemeMode ?? null).id,
);
// Soft reloads replace bootstrap without re-running startup terminal theme detection.
const [detectedThemeMode] = useState(() => bootstrap.initialThemeMode);
const [showAgentNotes, setShowAgentNotes] = useState(bootstrap.initialShowAgentNotes ?? false);
const [showLineNumbers, setShowLineNumbers] = useState(bootstrap.initialShowLineNumbers ?? true);
const [wrapLines, setWrapLines] = useState(bootstrap.initialWrapLines ?? false);
Expand All @@ -120,7 +122,7 @@ export function App({
const [resizeDragOriginX, setResizeDragOriginX] = useState<number | null>(null);
const [resizeStartWidth, setResizeStartWidth] = useState<number | null>(null);

const activeTheme = resolveTheme(themeId, bootstrap.initialThemeMode ?? null);
const activeTheme = resolveTheme(themeId, detectedThemeMode ?? null);
const review = useReviewController({ files: bootstrap.changeset.files });
const filteredFiles = review.visibleFiles;
const selectedFile = review.selectedFile;
Expand Down
72 changes: 72 additions & 0 deletions src/ui/AppHost.interactions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,29 @@ async function waitForFrame(
return frame;
}

/** Open the top-level Theme menu and wait for the expected active light theme marker. */
async function openThemeMenu(setup: Awaited<ReturnType<typeof testRender>>) {
await act(async () => {
await setup.mockInput.pressKey("F10");
});

const openedFrame = await waitForFrame(
setup,
(frame) => frame.includes("Toggle files/filter focus"),
12,
);
expect(openedFrame).toContain("Toggle files/filter focus");

for (let index = 0; index < 3; index += 1) {
await act(async () => {
await setup.mockInput.pressArrow("right");
});
await flush(setup);
}

return waitForFrame(setup, (frame) => frame.includes("[x] Paper"), 12);
}

async function pressHunkNavigationKey(
setup: Awaited<ReturnType<typeof testRender>>,
key: "]" | "[",
Expand Down Expand Up @@ -1255,6 +1278,55 @@ describe("App interactions", () => {
}
});

test("watch mode preserves the resolved auto theme after refreshing the file diff", async () => {
const dir = mkdtempSync(join(tmpdir(), "hunk-watch-theme-"));
const left = join(dir, "before.ts");
const right = join(dir, "after.ts");

writeFileSync(left, "export const answer = 41;\n");
writeFileSync(right, "export const answer = 42;\n");

const bootstrap = await loadAppBootstrap({
kind: "diff",
left,
right,
options: {
mode: "split",
theme: "auto",
watch: true,
},
});
// loadAppBootstrap does not do startup-time terminal theme detection in tests.
bootstrap.initialThemeMode = "light";

const setup = await testRender(<AppHost bootstrap={bootstrap} />, {
width: 220,
height: 20,
});

try {
await flush(setup);

writeFileSync(right, "export const answer = 42;\nexport const added = true;\n");

const refreshedFrame = await waitForFrame(
setup,
(currentFrame) => currentFrame.includes("export const added = true;"),
40,
);
expect(refreshedFrame).toContain("export const added = true;");

const menuFrame = await openThemeMenu(setup);
expect(menuFrame).toContain("[x] Paper");
expect(menuFrame).toContain("[ ] Graphite");
} finally {
await act(async () => {
setup.renderer.destroy();
});
rmSync(dir, { force: true, recursive: true });
}
});

test("a shows notes that are visible in the current review viewport", async () => {
const bootstrap = createBootstrap();
bootstrap.changeset.files[1]!.agent = {
Expand Down
Loading