critique 0.1.40 → 0.1.42

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/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ # 0.1.42
2
+
3
+ - New `--image` flag for all diff commands:
4
+ - Generates WebP images of terminal output (saved to /tmp)
5
+ - Splits long diffs into multiple images (70 lines per image)
6
+ - Uses takumi for high-performance image rendering
7
+ - `@takumi-rs/core` and `@takumi-rs/helpers` added as optional dependencies
8
+ - Library export: `import { renderTerminalToImages } from "critique/src/image.ts"`
9
+ - Web output: Use default theme to enable dark/light mode switching based on system preference
10
+ - `review` command:
11
+ - Improved AI prompt: order hunks by code flow, think upfront before writing, split heavy logic across sections
12
+ - Dependencies:
13
+ - Update opentui to `367a9408`
14
+
15
+ # 0.1.41
16
+
17
+ - `review` command:
18
+ - Filter `--resume` reviews by current working directory (only shows reviews from cwd or subdirectories)
19
+ - Use ACP `unstable_listSessions` for OpenCode instead of parsing JSON files directly
20
+ - Falls back to file-based parsing for Claude Code when ACP method unavailable
21
+ - Add instruction to always close code blocks before new text (fixes unclosed diagram blocks)
22
+
1
23
  # 0.1.40
2
24
 
3
25
  - `review` command:
package/bun.lock CHANGED
@@ -7,8 +7,8 @@
7
7
  "dependencies": {
8
8
  "@agentclientprotocol/sdk": "^0.12.0",
9
9
  "@clack/prompts": "1.0.0-alpha.9",
10
- "@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@302acd5f2860aaeecf5a1a60325c195e3a0597e4",
11
- "@opentui/react": "https://pkg.pr.new/anomalyco/opentui/@opentui/react@302acd5f2860aaeecf5a1a60325c195e3a0597e4",
10
+ "@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@367a94087821b3b5feedd35bbb57df43b10a286e",
11
+ "@opentui/react": "https://pkg.pr.new/anomalyco/opentui/@opentui/react@367a94087821b3b5feedd35bbb57df43b10a286e",
12
12
  "@parcel/watcher": "^2.5.1",
13
13
  "bun-pty": "^0.4.7",
14
14
  "cac": "^6.7.14",
@@ -27,6 +27,10 @@
27
27
  "typescript": "^5.9.3",
28
28
  "wrangler": "^4.19.1",
29
29
  },
30
+ "optionalDependencies": {
31
+ "@takumi-rs/core": "^0.65.0",
32
+ "@takumi-rs/helpers": "^0.65.0",
33
+ },
30
34
  },
31
35
  },
32
36
  "packages": {
@@ -222,21 +226,21 @@
222
226
 
223
227
  "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
224
228
 
225
- "@opentui/core": ["@opentui/core@https://pkg.pr.new/anomalyco/opentui/@opentui/core@302acd5f2860aaeecf5a1a60325c195e3a0597e4", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "@opentui/core-darwin-x64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "@opentui/core-linux-arm64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "@opentui/core-linux-x64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "@opentui/core-win32-arm64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "@opentui/core-win32-x64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }],
229
+ "@opentui/core": ["@opentui/core@https://pkg.pr.new/anomalyco/opentui/@opentui/core@367a94087821b3b5feedd35bbb57df43b10a286e", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@367a94087821b3b5feedd35bbb57df43b10a286e", "@opentui/core-darwin-x64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@367a94087821b3b5feedd35bbb57df43b10a286e", "@opentui/core-linux-arm64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@367a94087821b3b5feedd35bbb57df43b10a286e", "@opentui/core-linux-x64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@367a94087821b3b5feedd35bbb57df43b10a286e", "@opentui/core-win32-arm64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@367a94087821b3b5feedd35bbb57df43b10a286e", "@opentui/core-win32-x64": "https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@367a94087821b3b5feedd35bbb57df43b10a286e", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }],
226
230
 
227
- "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", {}],
231
+ "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@367a94087821b3b5feedd35bbb57df43b10a286e", {}],
228
232
 
229
- "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", {}],
233
+ "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@367a94087821b3b5feedd35bbb57df43b10a286e", {}],
230
234
 
231
- "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", {}],
235
+ "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@367a94087821b3b5feedd35bbb57df43b10a286e", {}],
232
236
 
233
- "@opentui/core-linux-x64": ["@opentui/core-linux-x64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", {}],
237
+ "@opentui/core-linux-x64": ["@opentui/core-linux-x64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@367a94087821b3b5feedd35bbb57df43b10a286e", {}],
234
238
 
235
- "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", {}],
239
+ "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@367a94087821b3b5feedd35bbb57df43b10a286e", {}],
236
240
 
237
- "@opentui/core-win32-x64": ["@opentui/core-win32-x64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@302acd5f2860aaeecf5a1a60325c195e3a0597e4", {}],
241
+ "@opentui/core-win32-x64": ["@opentui/core-win32-x64@https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@367a94087821b3b5feedd35bbb57df43b10a286e", {}],
238
242
 
239
- "@opentui/react": ["@opentui/react@https://pkg.pr.new/anomalyco/opentui/@opentui/react@302acd5f2860aaeecf5a1a60325c195e3a0597e4", { "dependencies": { "@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@302acd5f2860aaeecf5a1a60325c195e3a0597e4", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }],
243
+ "@opentui/react": ["@opentui/react@https://pkg.pr.new/anomalyco/opentui/@opentui/react@367a94087821b3b5feedd35bbb57df43b10a286e", { "dependencies": { "@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@367a94087821b3b5feedd35bbb57df43b10a286e", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }],
240
244
 
241
245
  "@parcel/watcher": ["@parcel/watcher@2.5.4", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.4", "@parcel/watcher-darwin-arm64": "2.5.4", "@parcel/watcher-darwin-x64": "2.5.4", "@parcel/watcher-freebsd-x64": "2.5.4", "@parcel/watcher-linux-arm-glibc": "2.5.4", "@parcel/watcher-linux-arm-musl": "2.5.4", "@parcel/watcher-linux-arm64-glibc": "2.5.4", "@parcel/watcher-linux-arm64-musl": "2.5.4", "@parcel/watcher-linux-x64-glibc": "2.5.4", "@parcel/watcher-linux-x64-musl": "2.5.4", "@parcel/watcher-win32-arm64": "2.5.4", "@parcel/watcher-win32-ia32": "2.5.4", "@parcel/watcher-win32-x64": "2.5.4" } }, "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ=="],
242
246
 
@@ -276,6 +280,26 @@
276
280
 
277
281
  "@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
278
282
 
283
+ "@takumi-rs/core": ["@takumi-rs/core@0.65.0", "", { "optionalDependencies": { "@takumi-rs/core-darwin-arm64": "0.65.0", "@takumi-rs/core-darwin-x64": "0.65.0", "@takumi-rs/core-linux-arm64-gnu": "0.65.0", "@takumi-rs/core-linux-arm64-musl": "0.65.0", "@takumi-rs/core-linux-x64-gnu": "0.65.0", "@takumi-rs/core-linux-x64-musl": "0.65.0", "@takumi-rs/core-win32-arm64-msvc": "0.65.0", "@takumi-rs/core-win32-x64-msvc": "0.65.0" } }, "sha512-lBNG+NRc602ul7Kxy9UohlbnFLVyloQP/DTxQzyNH+8khpdaIQTxpdHouzRzxf6upRDVDIVX5TG8oT16jKUAvQ=="],
284
+
285
+ "@takumi-rs/core-darwin-arm64": ["@takumi-rs/core-darwin-arm64@0.65.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Ii6ILOANG6IeGThsuYjG5azlHXyHWWdVbM1ps3+SJyUg2g4Qn+nTruKGqHQOLNdZ5+37vpTg4PWh79XrUvjXpw=="],
286
+
287
+ "@takumi-rs/core-darwin-x64": ["@takumi-rs/core-darwin-x64@0.65.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-SJGeFVQ2foVHpYL6EiD+jNJnvQmegDLbbJ/SzbaYemJ/kjza2vNDg251urXne6daQ+HoGXLjhQtzW1C5Nyyf5g=="],
288
+
289
+ "@takumi-rs/core-linux-arm64-gnu": ["@takumi-rs/core-linux-arm64-gnu@0.65.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YbwZzQTugFwvYErXs2W/QsngPSU5H6ZtYJNdbz9/qk8EEU6eB593XiBxE9/1vbq6Sb3mH84mPXElf1BmWJlvzw=="],
290
+
291
+ "@takumi-rs/core-linux-arm64-musl": ["@takumi-rs/core-linux-arm64-musl@0.65.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-iZpzfnD1/Gyof3p+fYUIBxNkDH9vQviOeihZ7yAtPbGmY4AbnOjoUBHDyJoKWE0el/x1W17efvNE7FuvzP0O5Q=="],
292
+
293
+ "@takumi-rs/core-linux-x64-gnu": ["@takumi-rs/core-linux-x64-gnu@0.65.0", "", { "os": "linux", "cpu": "x64" }, "sha512-kL4VclPYz7nmQuadCVSrAFvdy8LaJX5RPymG3upaHPcz0i7A8KmWY8jnQcDDkGQfPIi2lFncnt/sknCiaJ1t4Q=="],
294
+
295
+ "@takumi-rs/core-linux-x64-musl": ["@takumi-rs/core-linux-x64-musl@0.65.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2way2sF3p5bMlhGsDMmQGTPxtc37241POzAJ0bPRKR1fumylEAaADuXvN95qM4x9751iTke/wVhJOL28nV7asw=="],
296
+
297
+ "@takumi-rs/core-win32-arm64-msvc": ["@takumi-rs/core-win32-arm64-msvc@0.65.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yCZqiu1b3XXhDUE7OaA5RdVg/nYcZU1nzToY7ViE4fR8PqOK54Ad8qOAKYZCTr4FwCB4sWo88/R7K1n0nPFpfA=="],
298
+
299
+ "@takumi-rs/core-win32-x64-msvc": ["@takumi-rs/core-win32-x64-msvc@0.65.0", "", { "os": "win32", "cpu": "x64" }, "sha512-SAw224AhLcKa9+ttO4IxeTquOvyz8pnDeGugzmQZerYO18kOuOCiP1g4VRhM1drpEFN7bbnhSnIvrewg5m4akw=="],
300
+
301
+ "@takumi-rs/helpers": ["@takumi-rs/helpers@0.65.0", "", {}, "sha512-3BsiW0iP3Y7xUPmT/tJBClb59qTxtHEYHQUznXdtDA0qyN6YAWABA6U0DTd3la6XHMEDyKaHV263OL679FyPaA=="],
302
+
279
303
  "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
280
304
 
281
305
  "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "critique",
3
3
  "module": "src/diff.tsx",
4
4
  "type": "module",
5
- "version": "0.1.40",
5
+ "version": "0.1.42",
6
6
  "private": false,
7
7
  "bin": "./src/cli.tsx",
8
8
  "scripts": {
@@ -24,8 +24,8 @@
24
24
  "dependencies": {
25
25
  "@agentclientprotocol/sdk": "^0.12.0",
26
26
  "@clack/prompts": "1.0.0-alpha.9",
27
- "@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@302acd5f2860aaeecf5a1a60325c195e3a0597e4",
28
- "@opentui/react": "https://pkg.pr.new/anomalyco/opentui/@opentui/react@302acd5f2860aaeecf5a1a60325c195e3a0597e4",
27
+ "@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@367a94087821b3b5feedd35bbb57df43b10a286e",
28
+ "@opentui/react": "https://pkg.pr.new/anomalyco/opentui/@opentui/react@367a94087821b3b5feedd35bbb57df43b10a286e",
29
29
  "@parcel/watcher": "^2.5.1",
30
30
  "bun-pty": "^0.4.7",
31
31
  "cac": "^6.7.14",
@@ -35,5 +35,9 @@
35
35
  "picocolors": "^1.1.1",
36
36
  "react": "^19.2.0",
37
37
  "zustand": "^5.0.8"
38
+ },
39
+ "optionalDependencies": {
40
+ "@takumi-rs/core": "^0.65.0",
41
+ "@takumi-rs/helpers": "^0.65.0"
38
42
  }
39
43
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- // Preview script for ReviewApp component
3
- // Run with: bun run scripts/preview-review.tsx
4
- // Web mode: bun run scripts/preview-review.tsx --web
2
+ // Development preview script for testing ReviewApp styles without running AI.
3
+ // Renders example hunks and review data to preview TUI appearance.
4
+ // Run with: bun run scripts/preview-review.tsx (TUI) or --web (HTML upload).
5
5
 
6
6
  import { createCliRenderer } from "@opentui/core"
7
7
  import { createRoot } from "@opentui/react"
package/src/ansi-html.ts CHANGED
@@ -1,3 +1,7 @@
1
+ // ANSI terminal output to HTML converter for web preview generation.
2
+ // Uses ghostty-opentui to parse PTY output and generates responsive HTML documents
3
+ // with proper font scaling to fit terminal content within viewport width.
4
+
1
5
  import { ptyToJson, StyleFlags, type TerminalData, type TerminalLine, type TerminalSpan } from "ghostty-opentui"
2
6
 
3
7
  export interface AnsiToHtmlOptions {
package/src/cli.tsx CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env bun
2
+ // CLI entrypoint for the critique diff viewer.
3
+ // Provides TUI diff viewing, AI-powered review generation, and web preview upload.
4
+ // Commands: default (diff), review (AI analysis), web (HTML upload), pick (cherry-pick files).
5
+
2
6
  import { cac } from "cac";
3
7
  import {
4
8
  createRoot,
@@ -500,7 +504,8 @@ async function runReviewMode(
500
504
 
501
505
  // Write hunks to temp file for the render command
502
506
  const hunksFile = writeTempFile(JSON.stringify(hunks), "critique-hunks", ".json");
503
- const themeName = persistedState.themeName ?? defaultThemeName;
507
+ // For web, always use default theme (with auto dark/light inversion) unless explicitly overridden
508
+ const themeName = defaultThemeName;
504
509
 
505
510
  // Calculate rows needed based on hunks
506
511
  const totalLines = hunks.reduce((sum, h) => sum + h.lines.length, 0);
@@ -667,12 +672,12 @@ async function runResumeMode(options: ResumeModeOptions) {
667
672
 
668
673
  let reviewId = options.reviewId;
669
674
 
670
- // If no ID provided, show select
675
+ // If no ID provided, show select (filtered to current cwd and children)
671
676
  if (!reviewId) {
672
- const reviews = listReviews();
677
+ const reviews = listReviews(process.cwd());
673
678
 
674
679
  if (reviews.length === 0) {
675
- clack.log.warn("No saved reviews found");
680
+ clack.log.warn("No saved reviews found for this directory");
676
681
  clack.outro("");
677
682
  process.exit(0);
678
683
  }
@@ -830,7 +835,8 @@ async function runResumeMode(options: ResumeModeOptions) {
830
835
  }).join("\n");
831
836
  const yamlFile = writeTempFile(yamlContent, "critique-review", ".yaml");
832
837
 
833
- const themeName = persistedState.themeName ?? defaultThemeName;
838
+ // For web, always use default theme (with auto dark/light inversion) unless explicitly overridden
839
+ const themeName = defaultThemeName;
834
840
  const totalLines = review.hunks.reduce((sum, h) => sum + h.lines.length, 0);
835
841
  const baseRows = Math.max(200, totalLines * 2 + 100);
836
842
 
@@ -914,6 +920,16 @@ interface WebModeOptions {
914
920
  '--'?: string[];
915
921
  }
916
922
 
923
+ // Image mode handler
924
+ interface ImageModeOptions {
925
+ staged?: boolean;
926
+ commit?: string;
927
+ context?: string;
928
+ filter?: string;
929
+ theme?: string;
930
+ '--'?: string[];
931
+ }
932
+
917
933
  async function runWebMode(
918
934
  base: string | undefined,
919
935
  head: string | undefined,
@@ -939,9 +955,10 @@ async function runWebMode(
939
955
 
940
956
  const desktopCols = options.cols || 230;
941
957
  const mobileCols = options.mobileCols || 100;
958
+ // For web, always use default theme (with auto dark/light inversion) unless explicitly overridden via --theme
942
959
  const themeName = options.theme && themeNames.includes(options.theme)
943
960
  ? options.theme
944
- : persistedState.themeName ?? defaultThemeName;
961
+ : defaultThemeName;
945
962
 
946
963
  console.log("Capturing diff output...");
947
964
 
@@ -1005,6 +1022,118 @@ async function runWebMode(
1005
1022
  }
1006
1023
  }
1007
1024
 
1025
+ async function runImageMode(
1026
+ base: string | undefined,
1027
+ head: string | undefined,
1028
+ options: ImageModeOptions
1029
+ ) {
1030
+ const { renderTerminalToImages } = await import("./image.ts");
1031
+ const { writeTempFile, cleanupTempFile } = await import("./web-utils.ts");
1032
+
1033
+ const gitCommand = buildGitCommand({
1034
+ staged: options.staged,
1035
+ commit: options.commit,
1036
+ base,
1037
+ head,
1038
+ context: options.context,
1039
+ filter: options.filter,
1040
+ positionalFilters: options['--'],
1041
+ });
1042
+
1043
+ const themeName = options.theme && themeNames.includes(options.theme)
1044
+ ? options.theme
1045
+ : persistedState.themeName ?? defaultThemeName;
1046
+
1047
+ console.log("Capturing diff output...");
1048
+
1049
+ // Get the git diff
1050
+ const { stdout: gitDiff } = await execAsync(gitCommand, {
1051
+ encoding: "utf-8",
1052
+ });
1053
+
1054
+ if (!gitDiff.trim()) {
1055
+ console.log("No changes to display");
1056
+ process.exit(0);
1057
+ }
1058
+
1059
+ // Write diff to temp file
1060
+ const diffFile = writeTempFile(gitDiff, "critique-image-diff", ".patch");
1061
+
1062
+ // Build render command for image capture
1063
+ const renderCommand = [
1064
+ process.argv[1]!, // path to cli.tsx
1065
+ "web-render",
1066
+ diffFile,
1067
+ "--theme",
1068
+ themeName,
1069
+ "--cols",
1070
+ "120",
1071
+ "--rows",
1072
+ "10000",
1073
+ ];
1074
+
1075
+ console.log("Rendering to images...");
1076
+
1077
+ try {
1078
+ // Capture PTY output
1079
+ const decoder = new TextDecoder();
1080
+ let ansiOutput = "";
1081
+ const cols = 120;
1082
+ const rows = 10000;
1083
+
1084
+ const proc = Bun.spawn(["bun", ...renderCommand], {
1085
+ cwd: process.cwd(),
1086
+ env: {
1087
+ ...process.env,
1088
+ TERM: "xterm-256color",
1089
+ },
1090
+ terminal: {
1091
+ cols,
1092
+ rows,
1093
+ data(terminal, data) {
1094
+ ansiOutput += decoder.decode(data, { stream: true });
1095
+ },
1096
+ },
1097
+ });
1098
+
1099
+ await proc.exited;
1100
+ proc.terminal?.close();
1101
+ ansiOutput += decoder.decode();
1102
+
1103
+ // Clean up diff temp file
1104
+ cleanupTempFile(diffFile);
1105
+
1106
+ if (!ansiOutput.trim()) {
1107
+ console.error("No output captured");
1108
+ process.exit(1);
1109
+ }
1110
+
1111
+ // Strip terminal cleanup sequences
1112
+ const clearIdx = ansiOutput.lastIndexOf("\x1b[H\x1b[J");
1113
+ if (clearIdx > 0) {
1114
+ ansiOutput = ansiOutput.slice(0, clearIdx);
1115
+ }
1116
+
1117
+ // Render to images
1118
+ const result = await renderTerminalToImages(ansiOutput, {
1119
+ cols,
1120
+ themeName,
1121
+ });
1122
+
1123
+ console.log(`\nGenerated ${result.imageCount} image${result.imageCount === 1 ? "" : "s"}:`);
1124
+ for (const path of result.paths) {
1125
+ console.log(` ${path}`);
1126
+ }
1127
+
1128
+ process.exit(0);
1129
+ } catch (error: unknown) {
1130
+ cleanupTempFile(diffFile);
1131
+ const message = error instanceof Error ? error.message : String(error);
1132
+ console.error("Failed to generate images:", message);
1133
+ process.exit(1);
1134
+ }
1135
+ }
1136
+
1008
1137
  // Error boundary component
1009
1138
  interface ErrorBoundaryProps {
1010
1139
  children: React.ReactNode;
@@ -1350,6 +1479,7 @@ cli
1350
1479
  .option("--filter <pattern>", "Filter files by glob pattern (can be used multiple times)")
1351
1480
  .option("--theme <name>", "Theme to use for rendering")
1352
1481
  .option("--web [title]", "Generate web preview instead of TUI")
1482
+ .option("--image", "Generate images instead of TUI (saved to /tmp)")
1353
1483
  .option("--open", "Open in browser (with --web)")
1354
1484
  .option("--cols <cols>", "Desktop columns for web render", { default: 240 })
1355
1485
  .option("--mobile-cols <cols>", "Mobile columns for web render", { default: 100 })
@@ -1407,6 +1537,19 @@ cli
1407
1537
  return;
1408
1538
  }
1409
1539
 
1540
+ // If --image flag, delegate to image generation logic
1541
+ if (options.image) {
1542
+ await runImageMode(base, head, {
1543
+ staged: options.staged,
1544
+ commit: options.commit,
1545
+ context: options.context,
1546
+ filter: options.filter,
1547
+ theme: options.theme,
1548
+ '--': options['--'],
1549
+ });
1550
+ return;
1551
+ }
1552
+
1410
1553
  try {
1411
1554
  const gitCommand = buildGitCommand({
1412
1555
  staged: options.staged,
@@ -1,4 +1,6 @@
1
- // Shared DiffView component for rendering git diffs with syntax highlighting
1
+ // Shared DiffView component for rendering git diffs with syntax highlighting.
2
+ // Wraps opentui's <diff> element with theme-aware colors and syntax styles.
3
+ // Supports split and unified view modes with line numbers.
2
4
 
3
5
  import * as React from "react"
4
6
  import { SyntaxStyle } from "@opentui/core"
@@ -1,2 +1,4 @@
1
- // Shared components
1
+ // Shared React components for the TUI interface.
2
+ // Exports reusable components used across main diff view and review mode.
3
+
2
4
  export { DiffView, type DiffViewProps } from "./diff-view.tsx"
package/src/diff-utils.ts CHANGED
@@ -1,4 +1,6 @@
1
- // Shared utilities for diff processing between CLI commands
1
+ // Shared utilities for git diff processing across CLI commands.
2
+ // Builds git commands, parses diff files, detects filetypes for syntax highlighting,
3
+ // and provides helpers for unified/split view mode selection.
2
4
 
3
5
  export const IGNORED_FILES = [
4
6
  "pnpm-lock.yaml",
package/src/dropdown.tsx CHANGED
@@ -1,3 +1,7 @@
1
+ // Searchable dropdown component for file and theme selection in TUI.
2
+ // Supports keyboard navigation, fuzzy search filtering, and mouse interaction.
3
+ // Used by main diff view for file picker and theme picker overlays.
4
+
1
5
  import React, { useState, useEffect, useRef, useCallback, type ReactNode } from "react";
2
6
  import { useKeyboard } from "@opentui/react";
3
7
  import { TextAttributes, TextareaRenderable } from "@opentui/core";