From 5f1e2a2b21cf2a814315ed8e2fbd8acac4e8864e Mon Sep 17 00:00:00 2001 From: Saad Bash Date: Sun, 30 Aug 2026 23:53:43 -0400 Subject: [PATCH 1/7] feat: rename the stack diff layout to unified --- .changeset/unified-layout-language.md | 5 + AGENTS.md | 4 +- README.md | 30 ++-- benchmarks/README.md | 2 +- benchmarks/render-layout.ts | 14 +- docs/extensions.md | 10 +- docs/keybindings.md | 6 +- docs/opentui-component.md | 4 +- examples/4-ui-polish/README.md | 2 +- examples/5-pager-tour/after.ts | 2 +- examples/5-pager-tour/before.ts | 2 +- examples/7-opentui-component/README.md | 2 +- examples/7-opentui-component/support.tsx | 6 +- examples/8-opentui-primitives/README.md | 2 +- .../8-opentui-primitives/primitives-demo.tsx | 4 +- examples/extensions/github-pr/README.md | 2 +- .../jsx-file-view-gallery/README.md | 6 +- .../mixed-review/fixtures/after/README.md | 2 +- .../jsx-file-view-gallery/mixed-review/run.ts | 2 +- packages/hunk/README.md | 28 ++-- packages/hunk/src/app/cli.test.ts | 19 ++- packages/hunk/src/app/cli.ts | 34 ++--- .../hunk/src/core/run/commandCatalog.test.ts | 1 + packages/hunk/src/core/run/commandCatalog.ts | 5 +- packages/hunk/src/core/run/commandInputs.ts | 14 +- packages/hunk/src/core/run/config.test.ts | 11 +- packages/hunk/src/core/run/config.ts | 27 ++-- packages/hunk/src/extension-api/index.ts | 2 + packages/hunk/src/extension-api/types.ts | 22 ++- packages/hunk/src/opentui/HunkDiffBody.tsx | 12 +- .../hunk/src/opentui/HunkDiffView.test.tsx | 22 ++- .../hunk/src/opentui/HunkReviewStream.tsx | 4 +- packages/hunk/src/opentui/index.ts | 1 + packages/hunk/src/opentui/layout.ts | 9 ++ packages/hunk/src/opentui/types.ts | 5 +- packages/hunk/src/session/protocol.ts | 2 +- .../hunk/src/session/protocolSchemas.test.ts | 21 ++- packages/hunk/src/session/protocolSchemas.ts | 9 +- .../App.extension-command-controls.test.tsx | 2 +- .../src/ui/App.extension-runtime.test.tsx | 2 +- .../hunk/src/ui/App.extension-trust.test.tsx | 2 +- .../hunk/src/ui/AppHost.cursor-line.test.tsx | 4 +- .../src/ui/AppHost.edit-in-editor.test.tsx | 2 +- .../src/ui/AppHost.extension-dialogs.test.tsx | 2 +- .../ui/AppHost.extension-navigation.test.tsx | 2 +- .../src/ui/AppHost.extension-sidebar.test.tsx | 2 +- .../hunk/src/ui/AppHost.extensions.test.tsx | 20 +-- .../src/ui/AppHost.file-view-modes.test.tsx | 6 +- .../hunk/src/ui/AppHost.file-views.test.tsx | 20 +-- .../hunk/src/ui/AppHost.interactions.test.tsx | 12 +- .../hunk/src/ui/AppHost.keybindings.test.tsx | 2 +- .../src/ui/AppHost.keyboard-modes.test.tsx | 4 +- packages/hunk/src/ui/AppHost.reload.test.tsx | 12 +- .../hunk/src/ui/AppHost.responsive.test.tsx | 10 +- .../src/ui/AppHost.review-metadata.test.tsx | 8 +- .../hunk/src/ui/AppHost.selection.test.tsx | 4 +- packages/hunk/src/ui/AppHost.watch.test.tsx | 4 +- .../hunk/src/ui/AppHost.workspace.test.tsx | 26 ++-- .../components/panes/AgentInlineNote.test.tsx | 10 +- .../hunk/src/ui/components/panes/FileView.tsx | 2 +- .../ui/components/panes/copySelection.test.ts | 70 ++++----- .../src/ui/components/panes/copySelection.ts | 6 +- .../src/ui/components/ui-components.test.tsx | 72 +++++----- .../hunk/src/ui/currentReviewRefresh.test.ts | 4 +- .../hunk/src/ui/diff/CodeCellView.test.tsx | 57 ++++---- packages/hunk/src/ui/diff/CodeCellView.tsx | 100 ++++++------- .../hunk/src/ui/diff/CodeRowView.test.tsx | 26 ++-- packages/hunk/src/ui/diff/CodeRowView.tsx | 26 ++-- packages/hunk/src/ui/diff/DiffSectionBody.tsx | 6 +- packages/hunk/src/ui/diff/codeColumns.test.ts | 4 +- packages/hunk/src/ui/diff/codeColumns.ts | 8 +- .../src/ui/diff/codeRowAffordance.test.ts | 16 +-- .../hunk/src/ui/diff/codeRowLayout.test.ts | 30 ++-- packages/hunk/src/ui/diff/codeRowLayout.ts | 12 +- packages/hunk/src/ui/diff/diffRowModel.ts | 6 +- packages/hunk/src/ui/diff/diffRows.test.ts | 136 +++++++++--------- packages/hunk/src/ui/diff/diffRows.ts | 42 +++--- .../src/ui/diff/diffSectionGeometry.test.ts | 20 +-- .../hunk/src/ui/diff/diffSectionGeometry.ts | 2 +- .../hunk/src/ui/diff/diffSectionRowPlan.ts | 4 +- .../src/ui/diff/expandCollapsedRows.test.ts | 42 +++--- .../hunk/src/ui/diff/expandCollapsedRows.ts | 14 +- .../src/ui/diff/lineHighlightPaint.test.ts | 2 +- .../hunk/src/ui/diff/lineHighlightPaint.ts | 4 +- packages/hunk/src/ui/diff/plannedRowText.ts | 24 ++-- .../hunk/src/ui/diff/reviewRenderPlan.test.ts | 18 +-- packages/hunk/src/ui/diff/reviewRenderPlan.ts | 18 +-- .../hunk/src/ui/diff/reviewRowGeometry.ts | 2 +- packages/hunk/src/ui/diff/rowStyle.test.ts | 22 +-- packages/hunk/src/ui/diff/rowStyle.ts | 26 ++-- .../hunk/src/ui/fileViews/geometry.test.ts | 2 +- packages/hunk/src/ui/fileViews/geometry.ts | 4 +- ...useCurrentReviewRefreshController.test.tsx | 2 +- .../hooks/useExtensionReviewEvents.test.tsx | 17 ++- .../src/ui/hooks/useExtensionReviewEvents.ts | 7 +- .../hooks/useExtensionRuntimeBridge.test.tsx | 2 +- .../src/ui/hooks/useTerminalReview.test.tsx | 10 +- .../hunk/src/ui/lib/agentNoteGeometry.test.ts | 8 +- packages/hunk/src/ui/lib/appCommands.test.ts | 12 +- packages/hunk/src/ui/lib/appCommands.ts | 2 +- packages/hunk/src/ui/lib/appMenus.test.ts | 4 +- packages/hunk/src/ui/lib/appMenus.ts | 6 +- .../hunk/src/ui/lib/extensionCurrentLine.tsx | 14 +- packages/hunk/src/ui/lib/helpContent.test.ts | 2 +- packages/hunk/src/ui/lib/helpContent.ts | 4 +- packages/hunk/src/ui/lib/keymap.test.ts | 13 ++ packages/hunk/src/ui/lib/lineCursors.test.ts | 40 +++--- packages/hunk/src/ui/lib/responsive.test.ts | 16 +-- packages/hunk/src/ui/lib/responsive.ts | 6 +- packages/hunk/src/ui/lib/stml/guide.ts | 2 +- packages/hunk/src/ui/lib/ui-lib.test.ts | 4 +- .../hunk/src/ui/lib/viewportAnchor.test.ts | 48 +++---- packages/hunk/src/ui/lib/viewportAnchor.ts | 2 +- packages/hunk/src/ui/staticDiffPager.test.ts | 4 +- packages/hunk/src/ui/staticDiffPager.ts | 36 ++--- .../compare-release-benchmarks.test.ts | 26 ++++ .../benchmarks/compare-release-benchmarks.ts | 19 ++- scripts/dev/test-large-untracked-render.tsx | 2 +- scripts/launch-video/capture.ts | 14 +- scripts/packaging/check-pack.ts | 5 + test/pty/chrome.test.ts | 22 +-- test/pty/cursor-line.test.ts | 16 +-- test/pty/extensions-integration.test.ts | 40 +++--- test/pty/file-views-integration.test.ts | 20 +-- test/pty/highlighting.test.ts | 4 +- test/pty/key-routing.test.ts | 6 +- test/pty/layout.test.ts | 49 +++++-- test/pty/lifecycle.test.ts | 4 +- test/pty/moved-lines.test.ts | 2 +- test/pty/notes.test.ts | 14 +- test/pty/pager.test.ts | 4 +- .../pty/session-attention-integration.test.ts | 2 +- test/pty/watch.test.ts | 4 +- test/smoke/tty.test.ts | 14 +- website/MEDIA.md | 4 +- .../public/docs/images/agent-comments.webp | Bin 79564 -> 78436 bytes website/scripts/capture-media.ts | 2 +- .../marketing/FeatureShowcase.astro | 6 +- .../docs/docs/configure/layout-and-display.md | 8 +- .../content/docs/docs/extend/extension-api.md | 5 +- .../content/docs/docs/help/compatibility.md | 4 +- .../content/docs/docs/help/troubleshooting.md | 4 +- .../src/content/docs/docs/reference/cli.md | 2 +- .../src/content/docs/docs/reference/config.md | 4 +- .../docs/docs/start/keyboard-and-mouse.md | 2 +- .../content/docs/docs/start/quick-start.md | 2 +- website/src/data/comparisons.ts | 20 +-- website/src/data/vcsLandingPages.ts | 4 +- website/src/pages/compare/index.astro | 2 +- 149 files changed, 1073 insertions(+), 862 deletions(-) create mode 100644 .changeset/unified-layout-language.md create mode 100644 packages/hunk/src/opentui/layout.ts diff --git a/.changeset/unified-layout-language.md b/.changeset/unified-layout-language.md new file mode 100644 index 000000000..5ea4ea02a --- /dev/null +++ b/.changeset/unified-layout-language.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": minor +--- + +Rename the single-column diff layout to unified while retaining deprecated stack aliases for existing CLI, config, command, OpenTUI, and extension integrations. diff --git a/AGENTS.md b/AGENTS.md index 9c6dda1e5..79fe7795d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 `` 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. @@ -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. diff --git a/README.md b/README.md index 1811d16ec..ae880085e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 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 @@ -23,9 +23,9 @@ Hunk is a review-first terminal diff viewer for agent-authored changesets, built Split view with sidebar and inline AI notes - image + image
- Stacked view and mouse-selectable menus + Unified view and mouse-selectable menus @@ -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. @@ -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 diff --git a/benchmarks/README.md b/benchmarks/README.md index 7b2794cd3..5882f63ec 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -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. diff --git a/benchmarks/render-layout.ts b/benchmarks/render-layout.ts index 6d5effc07..15f0e6032 100644 --- a/benchmarks/render-layout.ts +++ b/benchmarks/render-layout.ts @@ -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"; @@ -16,7 +16,7 @@ function measureMs(run: () => void) { function measureScenario(name: string, files: ReturnType) { let splitRows = 0; - let stackRows = 0; + let unifiedRows = 0; let plannedRows = 0; const splitRowsMs = measureMs(() => { @@ -25,9 +25,9 @@ function measureScenario(name: string, files: ReturnType { + const unifiedRowsMs = measureMs(() => { for (const file of files) { - stackRows += buildStackRows(file, null, theme).length; + unifiedRows += buildUnifiedRows(file, null, theme).length; } }); @@ -50,12 +50,12 @@ function measureScenario(name: string, files: ReturnType - + ``` @@ -192,7 +192,7 @@ If you need direct access to Pierre's parser, `parsePatchFiles(...)` is still re | Prop | Type | Default | Notes | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------- | -| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. | +| `layout` | `"split" \| "unified" \| "stack"` | `"split"` | Chooses side-by-side or unified rendering; `stack` is a deprecated input alias. | | `width` | `number` | — | Required content width in terminal columns. | | `theme` | `"graphite" \| "midnight" \| "paper" \| "ember" \| "catppuccin-latte" \| "catppuccin-frappe" \| "catppuccin-macchiato" \| "catppuccin-mocha" \| "zenburn"` | `"graphite"` | Matches Hunk's built-in themes. | | `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. | diff --git a/examples/4-ui-polish/README.md b/examples/4-ui-polish/README.md index 99871555d..1a9effafd 100644 --- a/examples/4-ui-polish/README.md +++ b/examples/4-ui-polish/README.md @@ -12,4 +12,4 @@ hunk diff --files examples/4-ui-polish/before.tsx examples/4-ui-polish/after.tsx - renamed props and extracted button-label helper - nice intra-line emphasis in strings and labels -- a compact UI-focused diff that looks good in split and stacked layouts +- a compact UI-focused diff that looks good in split and unified layouts diff --git a/examples/5-pager-tour/after.ts b/examples/5-pager-tour/after.ts index e679fef16..221f494b9 100644 --- a/examples/5-pager-tour/after.ts +++ b/examples/5-pager-tour/after.ts @@ -34,7 +34,7 @@ export const tourLine24 = "Readable filler text makes navigation practice feel intentional instead of synthetic."; export const tourLine25 = "A few distinct hunks are better than one endless wall of edits."; export const tourLine26 = "Split view is great when you want both sides visible at once."; -export const tourLine27 = "Stack view can feel calmer when lines are long or heavily wrapped."; +export const tourLine27 = "Unified view can feel calmer when lines are long or heavily wrapped."; export const tourLine28 = "Pager mode should stay familiar to people who already live in git diff and less."; export const tourLine29 = diff --git a/examples/5-pager-tour/before.ts b/examples/5-pager-tour/before.ts index 5a902fdf0..7449add87 100644 --- a/examples/5-pager-tour/before.ts +++ b/examples/5-pager-tour/before.ts @@ -24,7 +24,7 @@ export const tourLine23 = "The best demos let you try scrolling without any setu export const tourLine24 = "Readable filler text keeps navigation practice from feeling fake."; export const tourLine25 = "A few distinct hunks are better than one endless wall of edits."; export const tourLine26 = "Split view is great when you want both sides visible at once."; -export const tourLine27 = "Stack view can feel calmer when lines are long."; +export const tourLine27 = "Unified view can feel calmer when lines are long."; export const tourLine28 = "Pager mode should stay familiar to people who live in git diff."; export const tourLine29 = "Clean copy changes are useful because they are easy to scan."; export const tourLine30 = "Repeated line structure makes scrolling behavior obvious."; diff --git a/examples/7-opentui-component/README.md b/examples/7-opentui-component/README.md index 48a87ba89..94d1c7677 100644 --- a/examples/7-opentui-component/README.md +++ b/examples/7-opentui-component/README.md @@ -16,7 +16,7 @@ bun run examples/7-opentui-component/from-patch.tsx - embedding `HunkDiffView` inside a normal OpenTUI app shell - building `diff.metadata` with `parseDiffFromFile` - parsing raw unified diff text with `parsePatchFiles` -- switching between split and stacked layouts with example shell controls +- switching between split and unified layouts with example shell controls - a scrollable terminal diff component that other OpenTUI apps can reuse The in-repo demos import from `../../packages/hunk/src/opentui` so they run from source. Published consumers should import from `hunkdiff/opentui` instead. diff --git a/examples/7-opentui-component/support.tsx b/examples/7-opentui-component/support.tsx index c1fe2c5e7..a732b4a08 100644 --- a/examples/7-opentui-component/support.tsx +++ b/examples/7-opentui-component/support.tsx @@ -76,9 +76,9 @@ function ExampleApp({ title, subtitle, diff, layout = "split" }: ExampleProps) { /> setActiveLayout("stack")} + active={activeLayout === "unified"} + label="Unified" + onPress={() => setActiveLayout("unified")} /> diff --git a/examples/8-opentui-primitives/README.md b/examples/8-opentui-primitives/README.md index 5a443929c..52b028ced 100644 --- a/examples/8-opentui-primitives/README.md +++ b/examples/8-opentui-primitives/README.md @@ -17,6 +17,6 @@ bun run examples/8-opentui-primitives/primitives-demo.tsx - `HunkReviewStream` for a multi-file review stream without Hunk's menu bar or global shortcuts - `HunkDiffFileHeader` and `HunkDiffBody` for a single-file view assembled by the host app - Host-owned window borders/chrome around each primitive so you can inspect component boundaries -- Host-owned state for selected file and split/stack layout +- Host-owned state for selected file and split/unified layout The in-repo demo imports from `../../packages/hunk/src/opentui` so it runs from source. Published consumers should import from `hunkdiff/opentui` instead. diff --git a/examples/8-opentui-primitives/primitives-demo.tsx b/examples/8-opentui-primitives/primitives-demo.tsx index 2f193825d..822212dfb 100644 --- a/examples/8-opentui-primitives/primitives-demo.tsx +++ b/examples/8-opentui-primitives/primitives-demo.tsx @@ -132,7 +132,7 @@ function PrimitivesDemoApp({ onQuit }: { onQuit: () => void }) { } if (key.name === "2") { - setLayout("stack"); + setLayout("unified"); return; } @@ -159,7 +159,7 @@ function PrimitivesDemoApp({ onQuit }: { onQuit: () => void }) { {padText( fitText( - " Hunk primitives as app windows — q quit · Tab next file · 1 split · 2 stack ", + " Hunk primitives as app windows — q quit · Tab next file · 1 split · 2 unified ", Math.max(1, terminal.width - 2), ), Math.max(1, terminal.width - 2), diff --git a/examples/extensions/github-pr/README.md b/examples/extensions/github-pr/README.md index 642e585f1..6ae4e728e 100644 --- a/examples/extensions/github-pr/README.md +++ b/examples/extensions/github-pr/README.md @@ -27,7 +27,7 @@ hunk gh https://github.com/modem-dev/hunk/pull/123 Quote the `owner/repo#number` form because an unquoted `#` starts a comment in some shells. Use `--` to pass options to the delegated `hunk patch` command: ```bash -hunk gh 123 --repo modem-dev/hunk -- --pager --mode stack +hunk gh 123 --repo modem-dev/hunk -- --pager --mode unified ``` Run `hunk gh --help` for the extension-owned help text. diff --git a/examples/extensions/jsx-file-view-gallery/README.md b/examples/extensions/jsx-file-view-gallery/README.md index eb097c91b..1ebfa02aa 100644 --- a/examples/extensions/jsx-file-view-gallery/README.md +++ b/examples/extensions/jsx-file-view-gallery/README.md @@ -9,7 +9,7 @@ Nested boxes, responsive meters, semantic color, and selected-hunk styling summa ```bash bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/jsx-file-view-gallery \ - --mode stack \ + --mode unified \ examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/before.ts \ examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/after.ts ``` @@ -21,7 +21,7 @@ The extension lazily reads both exact documents, associates changed opaque three ```bash bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/jsx-file-view-gallery \ - --mode stack \ + --mode unified \ examples/extensions/jsx-file-view-gallery/fixtures/css-palette/before.css \ examples/extensions/jsx-file-view-gallery/fixtures/css-palette/after.css ``` @@ -33,7 +33,7 @@ A conservative package-file parser highlights only the changed semantic-version ```bash bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/jsx-file-view-gallery \ - --mode stack \ + --mode unified \ examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/before/package.json \ examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/after/package.json ``` diff --git a/examples/extensions/jsx-file-view-gallery/mixed-review/fixtures/after/README.md b/examples/extensions/jsx-file-view-gallery/mixed-review/fixtures/after/README.md index cdba4d875..f0d78949a 100644 --- a/examples/extensions/jsx-file-view-gallery/mixed-review/fixtures/after/README.md +++ b/examples/extensions/jsx-file-view-gallery/mixed-review/fixtures/after/README.md @@ -30,7 +30,7 @@ The repository file may define a theme, default layout, ignored paths, and exten ```json { "theme": "midnight", - "layout": "stack", + "layout": "unified", "ignored": ["dist/**"], "extensions": ["./review-extensions"] } diff --git a/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts b/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts index f8a048379..ee291d6f0 100644 --- a/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts +++ b/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts @@ -79,7 +79,7 @@ try { "--extension", galleryRoot, "--mode", - "stack", + "unified", ], { cwd: demoRepo, stdio: "inherit", env: process.env }, ); diff --git a/packages/hunk/README.md b/packages/hunk/README.md index 1811d16ec..18b3252de 100644 --- a/packages/hunk/README.md +++ b/packages/hunk/README.md @@ -11,7 +11,7 @@ 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 @@ -25,7 +25,7 @@ Hunk is a review-first terminal diff viewer for agent-authored changesets, built image
- Stacked view and mouse-selectable menus + Unified view and mouse-selectable menus @@ -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. @@ -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 diff --git a/packages/hunk/src/app/cli.test.ts b/packages/hunk/src/app/cli.test.ts index 20311ffb3..856bf88d5 100644 --- a/packages/hunk/src/app/cli.test.ts +++ b/packages/hunk/src/app/cli.test.ts @@ -64,6 +64,8 @@ describe("parseCli", () => { expect(parsed.text).toContain("hunk skill path"); expect(parsed.text).toContain("Global options:"); expect(parsed.text).toContain("Common review options:"); + expect(parsed.text).toContain("layout mode: auto, split, unified"); + expect(parsed.text).not.toContain("layout mode: auto, split, stack"); expect(parsed.text).toContain("--file-gap"); expect(parsed.text).toContain("file separator rows, including ─"); expect(parsed.text).toContain("--hunk-gap"); @@ -387,14 +389,23 @@ describe("parseCli", () => { left, right, "--mode", - "stack", + "unified", ]); expect(parsed).toMatchObject({ kind: "diff", left, right, - options: { mode: "stack" }, + options: { mode: "unified" }, + }); + }); + + test("normalizes the deprecated stack layout input to unified", async () => { + const parsed = await parseCli(["bun", "hunk", "diff", "--mode", "stack"]); + + expect(parsed).toMatchObject({ + kind: "vcs", + options: { mode: "unified" }, }); }); @@ -1803,7 +1814,7 @@ describe("parseCli", () => { "right.ts", "src/example.ts", "--mode", - "stack", + "unified", ]); expect(parsed).toMatchObject({ @@ -1812,7 +1823,7 @@ describe("parseCli", () => { right: "right.ts", path: "src/example.ts", options: { - mode: "stack", + mode: "unified", }, }); if (parsed.kind !== "difftool") { diff --git a/packages/hunk/src/app/cli.ts b/packages/hunk/src/app/cli.ts index ca8197705..f5ed164a7 100644 --- a/packages/hunk/src/app/cli.ts +++ b/packages/hunk/src/app/cli.ts @@ -1,18 +1,20 @@ import { existsSync, realpathSync } from "node:fs"; import { resolve } from "node:path"; import { Command, Option } from "commander"; -import type { - CliInput, - CommonOptions, - CursorLine, - ExtensionManageCommandInput, - HelpCommandInput, - LayoutMode, - PagerCommandInput, - ParsedCliInput, - SelfUpdateCommandInput, - SessionCommentListType, - SessionCommentApplyItemInput, +import { + isLayoutModeInput, + normalizeLayoutModeInput, + type CliInput, + type CommonOptions, + type CursorLine, + type ExtensionManageCommandInput, + type HelpCommandInput, + type LayoutMode, + type PagerCommandInput, + type ParsedCliInput, + type SelfUpdateCommandInput, + type SessionCommentListType, + type SessionCommentApplyItemInput, } from "../core/run/commandInputs"; import { isBuiltInCliCommandName, @@ -97,7 +99,7 @@ export interface CliReferenceCommand { /** Review flags registered on every full-screen review command. */ export const COMMON_REVIEW_OPTIONS = [ - { flag: "--mode ", description: "layout mode: auto, split, stack", parse: "layout" }, + { flag: "--mode ", description: "layout mode: auto, split, unified", parse: "layout" }, { flag: "--cursor-line