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
67 changes: 6 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ Hunk is a review-first terminal diff viewer for agent-authored changesets, built
npm i -g hunkdiff
```

To use the reusable OpenTUI component in your own app:

```bash
npm i hunkdiff
```

Requirements:

- Node.js 18+
Expand Down Expand Up @@ -95,61 +89,6 @@ Hunk is optimized for reviewing a full changeset interactively.

## Advanced

### OpenTUI component

Hunk also publishes a reusable OpenTUI React diff component powered by the same terminal renderer that the app uses.

Runnable source examples live in [`examples/7-opentui-component`](examples/7-opentui-component/README.md).

Import it from `hunkdiff/opentui`:

```tsx
import { HunkDiffView, parseDiffFromFile } from "hunkdiff/opentui";

const metadata = parseDiffFromFile(
{
cacheKey: "before",
contents: "export const value = 1;\n",
name: "example.ts",
},
{
cacheKey: "after",
contents: "export const value = 2;\nexport const added = true;\n",
name: "example.ts",
},
{ context: 3 },
true,
);

<HunkDiffView
diff={{
id: "example",
metadata,
language: "typescript",
path: "example.ts",
}}
layout="split"
width={88}
theme="midnight"
/>;
```

Public API:

- `diff?: { id, metadata, language?, path?, patch? }`
- `layout?: "split" | "stack"`
- `width: number`
- `theme?: "graphite" | "midnight" | "paper" | "ember"`
- `showLineNumbers?: boolean`
- `showHunkHeaders?: boolean`
- `wrapLines?: boolean`
- `horizontalOffset?: number`
- `highlight?: boolean`
- `scrollable?: boolean`
- `selectedHunkIndex?: number`

The component re-exports `parseDiffFromFile`, `parsePatchFiles`, and `FileDiffMetadata` from `@pierre/diffs` so callers can build the `metadata` input without adding a second diff dependency.

### Config

You can persist preferences to a config file:
Expand Down Expand Up @@ -270,6 +209,12 @@ hunk diff --agent-context notes.json
hunk patch change.patch --agent-context notes.json
```

### OpenTUI component

Hunk also publishes `HunkDiffView` from `hunkdiff/opentui` for embedding the same diff renderer in your own OpenTUI app.

See [docs/opentui-component.md](docs/opentui-component.md) for install, API, and runnable examples.

## Examples

Ready-to-run demo diffs live in [`examples/`](examples/README.md).
Expand Down
134 changes: 134 additions & 0 deletions docs/opentui-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# OpenTUI component

`hunkdiff/opentui` exports `HunkDiffView`, a reusable terminal diff component built from the same renderer as the Hunk CLI.

Use it when you want Hunk's split or stack diff view inside your own OpenTUI app.

## Install

```bash
npm i hunkdiff @opentui/core @opentui/react react
```

`hunkdiff` declares OpenTUI and React as peer dependencies, so install them in your app.

## Quick start

```tsx
import { createCliRenderer } from "@opentui/core";
import { createRoot } from "@opentui/react";
import { HunkDiffView, parseDiffFromFile } from "hunkdiff/opentui";

const metadata = parseDiffFromFile(
{
cacheKey: "before",
contents: "export const value = 1;\n",
name: "example.ts",
},
{
cacheKey: "after",
contents: "export const value = 2;\nexport const added = true;\n",
name: "example.ts",
},
{ context: 3 },
true,
);

const renderer = await createCliRenderer({
useAlternateScreen: true,
useMouse: true,
exitOnCtrlC: true,
});
const root = createRoot(renderer);

root.render(
<HunkDiffView
diff={{
id: "example",
metadata,
language: "typescript",
path: "example.ts",
}}
layout="split"
width={88}
theme="midnight"
/>,
);
```

In a real app, derive `width` from your layout or `useTerminalDimensions()`.

## Building the `diff` input

`HunkDiffView` renders one file at a time. Pass a `diff` object shaped like this:

```ts
type HunkDiffFile = {
id: string;
metadata: FileDiffMetadata;
language?: string;
path?: string;
patch?: string;
};
```

### From before/after contents

Use `parseDiffFromFile(...)` when you already have the old and new file contents.

```tsx
import { parseDiffFromFile } from "hunkdiff/opentui";

const metadata = parseDiffFromFile(beforeFile, afterFile, { context: 3 }, true);
```

### From unified diff text

Use `parsePatchFiles(...)` when you already have a patch string.

```tsx
import { parsePatchFiles } from "hunkdiff/opentui";

const parsed = parsePatchFiles(patchText, "example:patch", true);
const metadata = parsed.flatMap((entry) => entry.files)[0];

if (!metadata) {
throw new Error("Expected at least one diff file.");
}
```

## Props

| Prop | Type | Default | Notes |
| ------------------- | ------------------------------------------------ | ------------ | ------------------------------------------------------------------------- |
| `diff` | `HunkDiffFile` | `undefined` | File to render. When omitted, the component shows an empty-state message. |
| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. |
| `width` | `number` | — | Required content width in terminal columns. |
| `theme` | `"graphite" \| "midnight" \| "paper" \| "ember"` | `"graphite"` | Matches Hunk's built-in themes. |
| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. |
| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. |
| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. |
| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. |
| `highlight` | `boolean` | `true` | Enables syntax highlighting. |
| `scrollable` | `boolean` | `true` | Set to `false` if your parent view owns scrolling. |
| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target. |

## Other exports

- `parseDiffFromFile`
- `parsePatchFiles`
- `FileDiffMetadata`
- `HUNK_DIFF_THEME_NAMES`
- `HunkDiffThemeName`
- `HunkDiffLayout`
- `HunkDiffFile`
- `HunkDiffViewProps`

`parseDiffFromFile`, `parsePatchFiles`, and `FileDiffMetadata` are re-exported from `@pierre/diffs` so you can build `metadata` without adding a second diff dependency.

## Examples

- Runnable demo overview: [`examples/README.md`](../examples/README.md)
- Component demos: [`examples/7-opentui-component/README.md`](../examples/7-opentui-component/README.md)

The in-repo demos import from `../../src/opentui` so they run from source. Published consumers should import from `hunkdiff/opentui`.
2 changes: 2 additions & 0 deletions examples/7-opentui-component/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Two minimal OpenTUI apps that embed `HunkDiffView` directly.

For package install and API details, see [OpenTUI component docs](../../docs/opentui-component.md).

## Run

```bash
Expand Down