@abelfubu/dv 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # `dv` — Diffview
2
+
3
+ A fast, keyboard-driven diff viewer for the terminal. Pipe any `git diff` into it and navigate changes with a TUI, copy selections, switch themes, and more.
4
+
5
+ ![license](https://img.shields.io/npm/l/@abelfubu/dv)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun install -g @abelfubu/dv
11
+ ```
12
+
13
+ Requires [Bun](https://bun.sh/).
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ # Pipe git diff output
19
+ git diff | dv
20
+
21
+ # Diff specific refs
22
+ git diff main...feature | dv
23
+
24
+ # Show working tree changes (default when stdin is empty)
25
+ dv
26
+
27
+ # Scrollback mode for non-TTY output
28
+ git diff | dv --scrollback
29
+
30
+ # Transparent background
31
+ dv --transparent
32
+
33
+ # Watch for changes
34
+ dv --watch
35
+ ```
36
+
37
+ ## Features
38
+
39
+ - **Interactive TUI** built with [`@opentuah/react`](https://github.com/opentuah/opentuah)
40
+ - **Unified and split view** modes (auto-switches based on terminal width)
41
+ - **Directory tree** sidebar for jumping between files
42
+ - **Keyboard selection** and **clipboard copy** of diff text
43
+ - **Syntax highlighting** via Tree-sitter parsers
44
+ - **Themes** — switch at runtime
45
+ - **Transparent background** mode
46
+ - **Scrollback output** when stdout isn't a TTY
47
+ - **Watch mode** for live diff updates
48
+ - **Submodule support** — dirty submodules shown inline
49
+ - **Untracked files** displayed via synthetic diffs
50
+
51
+ ## Keybindings
52
+
53
+ | Key | Action |
54
+ | --- | --- |
55
+ | `↑` / `↓` or `k` / `j` | Move cursor |
56
+ | `←` / `→` or `h` / `l` | Switch focus (tree / diff) |
57
+ | `Enter` | Jump to file under cursor |
58
+ | `Tab` | Toggle focus between tree and diff |
59
+ | `u` | Toggle unified / split view |
60
+ | `v` | Set selection anchor |
61
+ | `y` | Copy selected diff content |
62
+ | `t` | Cycle themes |
63
+ | `T` | Toggle transparent background |
64
+ | `c` | Change context lines |
65
+ | `r` | Refresh diff |
66
+ | `q` / `Esc` | Quit |
67
+
68
+ ## Development
69
+
70
+ ```bash
71
+ # Install dependencies
72
+ bun install
73
+
74
+ # Run in development
75
+ bun run cli
76
+
77
+ # Watch mode
78
+ bun run cli:watch
79
+
80
+ # Build
81
+ cd /Users/abelfubu/dev/diffview && bun run build
82
+
83
+ # Run tests
84
+ bun test
85
+ ```
86
+
87
+ ## Project Structure
88
+
89
+ ```
90
+ .
91
+ ├── src/
92
+ │ ├── cli.tsx # CLI entrypoint
93
+ │ ├── components/ # React/TUI components
94
+ │ ├── diff-*.ts # Diff parsing, cursor, copy utilities
95
+ │ ├── themes.ts # Theme definitions
96
+ │ ├── store.ts # Persistent state
97
+ │ └── parsers/ # Tree-sitter syntax parsers
98
+ ├── docs/ # ADRs and feature docs
99
+ ├── dist/ # Compiled output
100
+ └── package.json
101
+ ```
102
+
103
+ ## Docs
104
+
105
+ - [`docs/adr-cursor-selection.md`](docs/adr-cursor-selection.md)
106
+ - [`docs/transparent-background.md`](docs/transparent-background.md)
107
+ - [`CONTEXT.md`](CONTEXT.md) — domain glossary
108
+
109
+ ## License
110
+
111
+ MIT
@@ -43,7 +43,7 @@ describe("App copy notification", () => {
43
43
  });
44
44
  it("shows a transient success notification after copying selected lines", async () => {
45
45
  const parsedFiles = [createParsedFile("a.ts")];
46
- testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles }), {
46
+ testSetup = await testRender(_jsx(App, { parsedFiles: parsedFiles, copyNotificationDuration: 100 }), {
47
47
  width: 120,
48
48
  height: 16,
49
49
  });
@@ -61,15 +61,19 @@ describe("App copy notification", () => {
61
61
  testSetup.mockInput.pressKey("j");
62
62
  await testSetup.renderOnce();
63
63
  });
64
+ testSetup.mockInput.pressKey("y");
65
+ // The toast registers a post-render callback from an effect, and on the
66
+ // single-threaded Linux test renderer the state update isn't flushed in
67
+ // time for the next renderOnce(). Yield briefly before rendering.
68
+ await new Promise((resolve) => setTimeout(resolve, 50));
64
69
  await act(async () => {
65
- testSetup.mockInput.pressKey("y");
66
70
  await testSetup.renderOnce();
67
71
  });
68
72
  const frame = testSetup.captureCharFrame();
69
73
  expect(frame).toContain("Copied 2 lines");
70
74
  expect(frame).toContain("✓");
71
75
  // Wait for the auto-hide timeout to clear the notification.
72
- await new Promise((resolve) => setTimeout(resolve, 2500));
76
+ await new Promise((resolve) => setTimeout(resolve, 150));
73
77
  await act(async () => {
74
78
  await testSetup.renderOnce();
75
79
  });
package/dist/cli.d.ts CHANGED
@@ -4,6 +4,8 @@ import * as React from "react";
4
4
  import { type ParsedFile } from "./diff-utils.js";
5
5
  export interface AppProps {
6
6
  parsedFiles: ParsedFile[];
7
+ /** Duration (ms) the copy notification toast stays visible. Defaults to 2000. */
8
+ copyNotificationDuration?: number;
7
9
  }
8
- export declare function App({ parsedFiles }: AppProps): React.ReactNode;
10
+ export declare function App({ parsedFiles, copyNotificationDuration }: AppProps): React.ReactNode;
9
11
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";AAOA,OAAO,gCAAgC,CAAC;AAoBxC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgB/B,OAAO,EAoBN,KAAK,UAAU,EACf,MAAM,iBAAiB,CAAC;AAuIzB,MAAM,WAAW,QAAQ;IACxB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC1B;AAKD,wBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,SAAS,CAuzB9D"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";AAOA,OAAO,gCAAgC,CAAC;AAoBxC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgB/B,OAAO,EAoBN,KAAK,UAAU,EACf,MAAM,iBAAiB,CAAC;AAuIzB,MAAM,WAAW,QAAQ;IACxB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,iFAAiF;IACjF,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAClC;AAKD,wBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,wBAA+B,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,SAAS,CAuzB/F"}
package/dist/cli.js CHANGED
@@ -110,7 +110,7 @@ class ScrollAcceleration {
110
110
  }
111
111
  const SIDEBAR_GAP = 2;
112
112
  const APP_HORIZONTAL_PADDING = 2;
113
- export function App({ parsedFiles }) {
113
+ export function App({ parsedFiles, copyNotificationDuration = 2000 }) {
114
114
  const { width: initialWidth, height: initialHeight } = useTerminalDimensions();
115
115
  const [width, setWidth] = React.useState(initialWidth);
116
116
  const [_terminalHeight, setTerminalHeight] = React.useState(initialHeight);
@@ -231,7 +231,7 @@ export function App({ parsedFiles }) {
231
231
  copyNotificationTimeoutRef.current = setTimeout(() => {
232
232
  setCopyNotification(null);
233
233
  copyNotificationTimeoutRef.current = null;
234
- }, 2000);
234
+ }, copyNotificationDuration);
235
235
  return () => {
236
236
  if (copyNotificationTimeoutRef.current) {
237
237
  clearTimeout(copyNotificationTimeoutRef.current);
@@ -10,7 +10,7 @@ import {} from "../themes.js";
10
10
  */
11
11
  export function Toast({ message, type, title, theme, transparentBackground }) {
12
12
  const renderer = useRenderer();
13
- React.useEffect(() => {
13
+ React.useLayoutEffect(() => {
14
14
  const borderColor = RGBA.fromHex(type === "success" ? "#2d8a47" : "#c53b53");
15
15
  const bgColor = transparentBackground ? RGBA.fromInts(0, 0, 0, 0) : theme.background;
16
16
  const fgColor = theme.text;
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@abelfubu/dv",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "1.0.0",
5
5
  "license": "MIT",
6
- "bin": "./dist/cli.js",
6
+ "bin": {
7
+ "dv": "./dist/cli.js"
8
+ },
7
9
  "publishConfig": {
8
10
  "access": "public"
9
11
  },
@@ -17,8 +19,13 @@
17
19
  "build": "rm -rf dist && tsc",
18
20
  "prepublishOnly": "bun run build"
19
21
  },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/abelfubu/diffview.git"
25
+ },
20
26
  "devDependencies": {
21
27
  "@types/bun": "1.3.5",
28
+ "semantic-release": "^24.2.3",
22
29
  "tuistory": "^0.0.13",
23
30
  "typescript": "^5.9.3"
24
31
  },