Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/unified-layout-language.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hunkdiff": minor
---

Rename the single-column diff layout to unified, add canonical OpenTUI and extension API v23 fields, and retain deprecated stack compatibility across existing CLI, config, command, OpenTUI, and extension integrations.
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer ->
- Keep the app review-first: the main pane is a single top-to-bottom stream of all visible file diffs.
- The sidebar is for navigation. Selecting a file jumps to that file in the main review stream; it should not collapse the main pane to one file.
- Keep Pierre as the diff engine and renderer foundation. Do not switch the main renderer back to OpenTUI's built-in `<diff>` widget.
- Keep split and stack views terminal-native and driven from the same normalized diff model.
- Keep split and unified views terminal-native and driven from the same normalized diff model.
- Preserve mouse + keyboard parity for primary actions.
- Keep the chrome restrained: top menu bar, minimal borders, no redundant metadata headers.

Expand Down Expand Up @@ -165,7 +165,7 @@ ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer ->
## review behavior

- Default behavior is a multi-file review stream in sidebar order.
- Layout modes are `auto`, `split`, and `stack`. `auto` chooses split on wide terminals and stack
- Layout modes are `auto`, `split`, and `unified`. `auto` chooses split on wide terminals and unified
on narrow ones; explicit modes override it.
- `[` and `]` navigate hunks across the full review stream. Do not reintroduce `j`/`k` hunk navigation unless the user asks.
- Agent context belongs beside the code, not hidden in a separate mode or workflow.
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ Hunk is a review-first terminal diff viewer for agent-authored changesets, built

- multi-file review stream with sidebar navigation
- inline AI and agent annotations beside the code
- split, stack, and responsive auto layouts
- split, unified, and responsive auto layouts
- watch mode for auto-reloading file and Git-backed reviews
- keyboard, mouse, pager, and Git difftool support

<table>
<tr>
<td width="60%" align="center">
<img width="845" alt="image" src="https://github.com/user-attachments/assets/35605618-be3f-479e-b6e0-edb089910651" />
<img width="845" alt="image" src="https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/review-stream.webp" />
<br />
<sub>Split view with sidebar and inline AI notes</sub>
</td>
<td width="40%" align="center">
<img width="507"alt="image" src="https://github.com/user-attachments/assets/92eb8993-f044-436d-a038-8139da5ad8de" />
<img width="507"alt="image" src="https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/agent-comments.webp" />
<br />
<sub>Stacked view and mouse-selectable menus</sub>
<sub>Unified view and mouse-selectable menus</sub>
</td>
</tr>
</table>
Expand Down Expand Up @@ -135,17 +135,17 @@ For the full live-session and `--agent-context` workflow guide, see [the agent w

## Feature comparison

| Capability | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) |
| ---------------------------------- | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
| Review-first interactive UI | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Multi-file review stream + sidebar | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Inline agent / AI annotations | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Responsive auto split/stack layout | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Mouse support inside the viewer | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Runtime view toggles | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Syntax highlighting | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Structural diffing | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Pager-compatible mode | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Capability | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) |
| ------------------------------------ | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
| Review-first interactive UI | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Multi-file review stream + sidebar | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Inline agent / AI annotations | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Responsive auto split/unified layout | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Mouse support inside the viewer | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Runtime view toggles | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Syntax highlighting | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Structural diffing | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Pager-compatible mode | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |

Hunk is optimized for reviewing a full changeset interactively.

Expand All @@ -162,7 +162,7 @@ Example:

```toml
theme = "github-dark-default" # any built-in theme id, auto, or custom
mode = "auto" # auto, split, stack
mode = "auto" # auto, split, unified
vcs = "git" # git, jj, sl
watch = false
exclude_untracked = false
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ bun run bench:competitors
- `bootstrap-load.ts` — measures bootstrap and git-loader cost on a synthetic large repo, including file-pair bootstrap.
- `working-tree-load.ts` — measures git working-tree loads across small, medium, large, many-untracked, and few-large-untracked repos.
- `changeset-parse.ts` — measures patch normalization, Pierre parsing, patch chunking, and normalized `DiffFile` construction for many-small-files, balanced, and large-single-file patches.
- `render-layout.ts` — measures pure split/stack row building, section geometry, and review-plan construction for many-small-files, balanced, and large-single-file streams.
- `render-layout.ts` — measures pure split/unified row building, section geometry, and review-plan construction for many-small-files, balanced, and large-single-file streams.
- `highlight-prefetch.ts` — measures selected-file highlight startup and adjacent prefetch readiness.
- `worker-highlight-cache.ts` — measures a cold worker highlight against an immediate compact-result cache hit.
- `highlight-cache-layers.ts` — measures a resident terminal-cache hit against a worker-cache revisit after the terminal cache evicts the diff.
Expand Down
14 changes: 7 additions & 7 deletions benchmarks/render-layout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Benchmark pure diff row/layout planning across split, stack, and size-shape cases.
// Benchmark pure diff row/layout planning across split, unified, and size-shape cases.
import { performance } from "perf_hooks";
import { buildSplitRows, buildStackRows } from "../packages/hunk/src/ui/diff/diffRows";
import { buildSplitRows, buildUnifiedRows } from "../packages/hunk/src/ui/diff/diffRows";
import { buildReviewRenderPlan } from "../packages/hunk/src/ui/diff/reviewRenderPlan";
import { measureDiffSectionGeometry } from "../packages/hunk/src/ui/diff/diffSectionGeometry";
import { resolveTheme } from "../packages/hunk/src/ui/themes";
Expand All @@ -16,7 +16,7 @@ function measureMs(run: () => void) {

function measureScenario(name: string, files: ReturnType<typeof createLargeSplitStreamFiles>) {
let splitRows = 0;
let stackRows = 0;
let unifiedRows = 0;
let plannedRows = 0;

const splitRowsMs = measureMs(() => {
Expand All @@ -25,9 +25,9 @@ function measureScenario(name: string, files: ReturnType<typeof createLargeSplit
}
});

const stackRowsMs = measureMs(() => {
const unifiedRowsMs = measureMs(() => {
for (const file of files) {
stackRows += buildStackRows(file, null, theme).length;
unifiedRows += buildUnifiedRows(file, null, theme).length;
}
});

Expand All @@ -50,12 +50,12 @@ function measureScenario(name: string, files: ReturnType<typeof createLargeSplit
});

console.log(`METRIC ${name}_split_rows_ms=${splitRowsMs.toFixed(2)}`);
console.log(`METRIC ${name}_stack_rows_ms=${stackRowsMs.toFixed(2)}`);
console.log(`METRIC ${name}_unified_rows_ms=${unifiedRowsMs.toFixed(2)}`);
console.log(`METRIC ${name}_geometry_ms=${geometryMs.toFixed(2)}`);
console.log(`METRIC ${name}_review_plan_ms=${reviewPlanMs.toFixed(2)}`);
console.log(`METRIC ${name}_files=${files.length}`);
console.log(`METRIC ${name}_split_rows=${splitRows}`);
console.log(`METRIC ${name}_stack_rows=${stackRows}`);
console.log(`METRIC ${name}_unified_rows=${unifiedRows}`);
console.log(`METRIC ${name}_planned_rows=${plannedRows}`);
}

Expand Down
Loading
Loading