critique 0.1.103 → 0.1.105
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/AGENTS.md +3 -6
- package/CHANGELOG.md +17 -0
- package/package.json +5 -4
- package/scripts/preview-review.tsx +2 -2
- package/src/ansi-html.ts +2 -2
- package/src/ansi-output.test.ts +2 -2
- package/src/ansi-output.ts +2 -2
- package/src/cli-scroll.test.tsx +1 -1
- package/src/cli.tsx +7 -3
- package/src/components/diff-view.test.tsx +1 -1
- package/src/components/diff-view.tsx +1 -1
- package/src/diff-utils.ts +1 -1
- package/src/directory-tree.test.tsx +1 -1
- package/src/dropdown.test.tsx +1 -1
- package/src/dropdown.tsx +2 -2
- package/src/hooks/use-copy-selection.ts +1 -1
- package/src/image.ts +2 -3
- package/src/opentui-image.ts +2 -2
- package/src/patch-terminal-dimensions.ts +51 -0
- package/src/review/review-app.test.tsx +1 -1
- package/src/review/review-app.tsx +2 -2
- package/src/review/stream-display.test.tsx +1 -1
- package/src/review/stream-display.tsx +1 -1
- package/src/stdin-pager.test.ts +420 -0
- package/src/themes.ts +1 -1
- package/src/web-utils.test.ts +3 -3
- package/src/web-utils.tsx +14 -14
- package/tsconfig.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -17,8 +17,8 @@ gh pr view 536 -R anomalyco/opentui --json commits --jq '.commits[-1].oid[:40]'
|
|
|
17
17
|
then use it in package.json:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
https://pkg.pr.new/anomalyco/opentui/@
|
|
21
|
-
https://pkg.pr.new/anomalyco/opentui/@
|
|
20
|
+
https://pkg.pr.new/anomalyco/opentui/@opentuah/core@<hash>
|
|
21
|
+
https://pkg.pr.new/anomalyco/opentui/@opentuah/react@<hash>
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
YOU MUST ALWAYS use the commit hash 40 characters long when changing the pkg.pr.new url! not the pr number!
|
|
@@ -134,10 +134,7 @@ npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)
|
|
|
134
134
|
|
|
135
135
|
## opentui fork
|
|
136
136
|
|
|
137
|
-
we are using an opentui fork
|
|
138
|
-
|
|
139
|
-
"@opentui/core": "npm:@opentuah/core@^0.1.80",
|
|
140
|
-
"@opentui/react": "npm:@opentuah/react@^0.1.80",
|
|
137
|
+
we are using an opentui fork published as `@opentuah`. imports use `@opentuah/core` and `@opentuah/react` directly (no npm alias remapping).
|
|
141
138
|
|
|
142
139
|
To find my opentui folder with that fork see kimaki projects via kimaki cli, the one named opentui.
|
|
143
140
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
# 0.1.105
|
|
2
|
+
|
|
3
|
+
- `critique --stdin` / `critique --web`:
|
|
4
|
+
- Only show 'URL is private' notice when generating with --web (fix notice appearing in scrollback/pager output like lazygit where it makes no sense)
|
|
5
|
+
- Tests:
|
|
6
|
+
- Add comprehensive integration tests for --stdin pager mode using tuistory (10 test cases covering empty diffs, multiple files, renames, binary files, narrow terminals, etc.)
|
|
7
|
+
- Rewrite lazygit pager test as real integration test that launches critique in a PTY and verifies scrollback output
|
|
8
|
+
|
|
9
|
+
# 0.1.104
|
|
10
|
+
|
|
11
|
+
- `critique --stdin` (pager mode):
|
|
12
|
+
- Force scrollback mode for `--stdin` pager usage to fix lazygit integration (#25)
|
|
13
|
+
- When used as a pager (e.g. `critique --stdin` in lazygit), critique now correctly outputs static colored text instead of interactive TUI escape sequences
|
|
14
|
+
- `critique`:
|
|
15
|
+
- Fix crash in Bun compiled binaries where terminal TTY `columns`/`rows` report as 0, causing NaN framebuffer dimensions
|
|
16
|
+
- Replace `@opentui/*` npm alias remapping with direct `@opentuah/*` imports
|
|
17
|
+
|
|
1
18
|
# 0.1.103
|
|
2
19
|
|
|
3
20
|
- Syntax highlighting:
|
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.
|
|
5
|
+
"version": "0.1.105",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
8
8
|
"bin": "./src/cli.tsx",
|
|
@@ -22,17 +22,18 @@
|
|
|
22
22
|
"hono": "^4.7.10",
|
|
23
23
|
"sharp": "^0.34.5",
|
|
24
24
|
"stripe": "^20.2.0",
|
|
25
|
+
"tuistory": "^0.0.13",
|
|
25
26
|
"typescript": "^5.9.3",
|
|
26
27
|
"wrangler": "^4.19.1"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"@agentclientprotocol/sdk": "^0.13.1",
|
|
30
31
|
"@clack/prompts": "1.0.0-alpha.9",
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
32
|
+
"@opentuah/core": "0.1.95",
|
|
33
|
+
"@opentuah/react": "0.1.95",
|
|
33
34
|
"@parcel/watcher": "^2.5.6",
|
|
34
|
-
"goke": "^6.1.3",
|
|
35
35
|
"diff": "^8.0.2",
|
|
36
|
+
"goke": "^6.1.3",
|
|
36
37
|
"js-yaml": "^4.1.1",
|
|
37
38
|
"marked": "^17.0.1",
|
|
38
39
|
"picocolors": "^1.1.1",
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// Renders example hunks and review data to preview TUI appearance.
|
|
4
4
|
// Run with: bun run scripts/preview-review.tsx (TUI) or --web (HTML upload).
|
|
5
5
|
|
|
6
|
-
import { createCliRenderer, addDefaultParsers } from "@
|
|
6
|
+
import { createCliRenderer, addDefaultParsers } from "@opentuah/core"
|
|
7
7
|
import parsersConfig from "../parsers-config.ts"
|
|
8
8
|
|
|
9
9
|
// Register custom syntax highlighting parsers
|
|
10
10
|
addDefaultParsers(parsersConfig.parsers)
|
|
11
|
-
import { createRoot } from "@
|
|
11
|
+
import { createRoot } from "@opentuah/react"
|
|
12
12
|
import * as React from "react"
|
|
13
13
|
import { ReviewApp, ReviewAppView } from "../src/review/review-app.tsx"
|
|
14
14
|
import { createHunk } from "../src/review/hunk-parser.ts"
|
package/src/ansi-html.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Uses opentui's test renderer to capture structured span data and generates responsive HTML documents
|
|
3
3
|
// with proper font scaling to fit terminal content within viewport width.
|
|
4
4
|
|
|
5
|
-
import { TextAttributes, rgbToHex, type RGBA } from "@
|
|
6
|
-
import type { CapturedFrame, CapturedLine, CapturedSpan } from "@
|
|
5
|
+
import { TextAttributes, rgbToHex, type RGBA } from "@opentuah/core"
|
|
6
|
+
import type { CapturedFrame, CapturedLine, CapturedSpan } from "@opentuah/core"
|
|
7
7
|
|
|
8
8
|
export interface ToHtmlOptions {
|
|
9
9
|
/** Background color for the container */
|
package/src/ansi-output.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect } from "bun:test"
|
|
2
2
|
import { spanToAnsi, frameToAnsi } from "./ansi-output.ts"
|
|
3
|
-
import { RGBA } from "@
|
|
4
|
-
import type { CapturedFrame, CapturedSpan, CapturedLine } from "@
|
|
3
|
+
import { RGBA } from "@opentuah/core"
|
|
4
|
+
import type { CapturedFrame, CapturedSpan, CapturedLine } from "@opentuah/core"
|
|
5
5
|
|
|
6
6
|
const themeBg = RGBA.fromValues(0, 0, 0, 1) // Black background
|
|
7
7
|
|
package/src/ansi-output.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Falls back gracefully: truecolor → 256 → 16 → plain text.
|
|
4
4
|
|
|
5
5
|
import supportsColor from "supports-color"
|
|
6
|
-
import { TextAttributes, type RGBA } from "@
|
|
7
|
-
import type { CapturedFrame, CapturedSpan, CapturedLine } from "@
|
|
6
|
+
import { TextAttributes, type RGBA } from "@opentuah/core"
|
|
7
|
+
import type { CapturedFrame, CapturedSpan, CapturedLine } from "@opentuah/core"
|
|
8
8
|
|
|
9
9
|
// Color support levels: 0=none, 1=16 colors, 2=256 colors, 3=truecolor
|
|
10
10
|
type ColorLevel = 0 | 1 | 2 | 3
|
package/src/cli-scroll.test.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { afterEach, describe, expect, it } from "bun:test"
|
|
5
5
|
import { act } from "react"
|
|
6
|
-
import { testRender } from "@
|
|
6
|
+
import { testRender } from "@opentuah/react/test-utils"
|
|
7
7
|
import { App } from "./cli.tsx"
|
|
8
8
|
import type { ParsedFile } from "./diff-utils.ts"
|
|
9
9
|
|
package/src/cli.tsx
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
// Provides TUI diff viewing, AI-powered review generation, and web preview upload.
|
|
4
4
|
// Commands: default (diff), review (AI analysis), web (HTML upload), pick (cherry-pick files).
|
|
5
5
|
|
|
6
|
+
// Must be first import: patches process.stdout.columns/rows for Bun compiled binaries
|
|
7
|
+
// where they incorrectly return 0 instead of actual terminal dimensions.
|
|
8
|
+
import "./patch-terminal-dimensions.ts";
|
|
9
|
+
|
|
6
10
|
import { goke, wrapJsonSchema } from "goke";
|
|
7
11
|
import {
|
|
8
12
|
createRoot,
|
|
@@ -11,7 +15,7 @@ import {
|
|
|
11
15
|
useOnResize,
|
|
12
16
|
useRenderer,
|
|
13
17
|
useTerminalDimensions,
|
|
14
|
-
} from "@
|
|
18
|
+
} from "@opentuah/react";
|
|
15
19
|
import { useCopySelection } from "./hooks/use-copy-selection.ts";
|
|
16
20
|
import * as React from "react";
|
|
17
21
|
import { exec, execSync } from "child_process";
|
|
@@ -22,7 +26,7 @@ import {
|
|
|
22
26
|
ScrollBoxRenderable,
|
|
23
27
|
BoxRenderable,
|
|
24
28
|
addDefaultParsers,
|
|
25
|
-
} from "@
|
|
29
|
+
} from "@opentuah/core";
|
|
26
30
|
import parsersConfig from "../parsers-config.ts";
|
|
27
31
|
|
|
28
32
|
// Register custom syntax highlighting parsers
|
|
@@ -1826,7 +1830,7 @@ cli
|
|
|
1826
1830
|
return;
|
|
1827
1831
|
}
|
|
1828
1832
|
|
|
1829
|
-
if (options.scrollback || !process.stdout.isTTY) {
|
|
1833
|
+
if (options.scrollback || options.stdin || !process.stdout.isTTY) {
|
|
1830
1834
|
// For scrollback, prefer terminal width over --cols default (240 is for web)
|
|
1831
1835
|
const scrollbackCols = process.stdout.columns || parseInt(options.cols) || 120;
|
|
1832
1836
|
await runScrollbackMode(cleanedDiff, {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { afterEach, describe, expect, it } from "bun:test"
|
|
5
5
|
import { act } from "react"
|
|
6
|
-
import { testRender } from "@
|
|
6
|
+
import { testRender } from "@opentuah/react/test-utils"
|
|
7
7
|
import { DiffView } from "./diff-view.tsx"
|
|
8
8
|
import { useAppStore } from "../store.ts"
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Supports split and unified view modes with line numbers.
|
|
4
4
|
|
|
5
5
|
import * as React from "react"
|
|
6
|
-
import { SyntaxStyle } from "@
|
|
6
|
+
import { SyntaxStyle } from "@opentuah/core"
|
|
7
7
|
import { getSyntaxTheme, getResolvedTheme, rgbaToHex } from "../themes.ts"
|
|
8
8
|
|
|
9
9
|
export interface DiffViewProps {
|
package/src/diff-utils.ts
CHANGED
|
@@ -459,7 +459,7 @@ export function processFiles<T extends ParsedFile>(
|
|
|
459
459
|
|
|
460
460
|
/**
|
|
461
461
|
* Detect filetype from filename for syntax highlighting
|
|
462
|
-
* Maps to tree-sitter parsers available in @
|
|
462
|
+
* Maps to tree-sitter parsers available in @opentuah/core and parsers-config.ts
|
|
463
463
|
*/
|
|
464
464
|
export function detectFiletype(filePath: string): string | undefined {
|
|
465
465
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Uses opentui test renderer with captureCharFrame() for visual testing
|
|
3
3
|
|
|
4
4
|
import { afterEach, describe, expect, it } from "bun:test"
|
|
5
|
-
import { testRender } from "@
|
|
5
|
+
import { testRender } from "@opentuah/react/test-utils"
|
|
6
6
|
import { buildDirectoryTree, type TreeFileInfo, type TreeNode } from "./directory-tree.ts"
|
|
7
7
|
import { DirectoryTreeView } from "./components/directory-tree-view.tsx"
|
|
8
8
|
|
package/src/dropdown.test.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { afterEach, describe, expect, it } from "bun:test"
|
|
3
3
|
import { act } from "react"
|
|
4
|
-
import { testRender } from "@
|
|
4
|
+
import { testRender } from "@opentuah/react/test-utils"
|
|
5
5
|
import Dropdown, { filterDropdownOptions } from "./dropdown.tsx"
|
|
6
6
|
import { getResolvedTheme } from "./themes.ts"
|
|
7
7
|
|
package/src/dropdown.tsx
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Used by main diff view for file picker and theme picker overlays.
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
|
|
6
|
-
import { useKeyboard } from "@
|
|
7
|
-
import { TextAttributes, TextareaRenderable } from "@
|
|
6
|
+
import { useKeyboard } from "@opentuah/react";
|
|
7
|
+
import { TextAttributes, TextareaRenderable } from "@opentuah/core";
|
|
8
8
|
import { type ResolvedTheme, rgbaToHex } from "./themes";
|
|
9
9
|
|
|
10
10
|
export interface DropdownOption {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Automatically copies selected text to clipboard when user releases mouse button.
|
|
3
3
|
// Uses native clipboard commands (pbcopy, xclip, etc.) with OSC52 fallback.
|
|
4
4
|
|
|
5
|
-
import { useRenderer } from "@
|
|
5
|
+
import { useRenderer } from "@opentuah/react"
|
|
6
6
|
import { spawn } from "child_process"
|
|
7
7
|
|
|
8
8
|
/**
|
package/src/image.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Uses opentui-image.ts for generic frame-to-image conversion.
|
|
3
3
|
// Adds theme resolution and diff/review-specific rendering.
|
|
4
4
|
|
|
5
|
-
import type { CapturedFrame } from "@
|
|
5
|
+
import type { CapturedFrame } from "@opentuah/core"
|
|
6
6
|
import { getResolvedTheme, rgbaToHex } from "./themes.ts"
|
|
7
7
|
import {
|
|
8
8
|
renderFrameToImage,
|
|
@@ -334,12 +334,11 @@ export async function renderDiffToOgImage(
|
|
|
334
334
|
const charWidth = fontSize * 0.6
|
|
335
335
|
const cols = options.cols ?? Math.floor(contentWidth / charWidth)
|
|
336
336
|
|
|
337
|
-
// Render diff to captured frame
|
|
337
|
+
// Render diff to captured frame
|
|
338
338
|
const frame = await renderDiffToFrame(diffContent, {
|
|
339
339
|
cols,
|
|
340
340
|
maxRows: 200,
|
|
341
341
|
themeName,
|
|
342
|
-
showNotice: false,
|
|
343
342
|
})
|
|
344
343
|
|
|
345
344
|
// Convert frame to OG image
|
package/src/opentui-image.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { tmpdir } from "os"
|
|
5
5
|
import { join } from "path"
|
|
6
6
|
import fs from "fs"
|
|
7
|
-
import { TextAttributes, rgbToHex, type RGBA } from "@
|
|
8
|
-
import type { CapturedFrame, CapturedLine, CapturedSpan } from "@
|
|
7
|
+
import { TextAttributes, rgbToHex, type RGBA } from "@opentuah/core"
|
|
8
|
+
import type { CapturedFrame, CapturedLine, CapturedSpan } from "@opentuah/core"
|
|
9
9
|
|
|
10
10
|
// ─────────────────────────────────────────────────────────────
|
|
11
11
|
// Types
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Polyfill for terminal dimensions in Bun compiled binaries.
|
|
2
|
+
// Bun's `--compile` produces binaries where process.stdout.columns/rows are 0
|
|
3
|
+
// instead of the actual terminal size (even when isTTY is true).
|
|
4
|
+
// This must be imported before any opentui imports, since opentui reads
|
|
5
|
+
// stdout.columns at init time and uses `?? fallback` which doesn't catch 0.
|
|
6
|
+
|
|
7
|
+
import { execSync } from "child_process"
|
|
8
|
+
|
|
9
|
+
function getTerminalSize(): { cols: number; rows: number } | null {
|
|
10
|
+
try {
|
|
11
|
+
const cols = parseInt(
|
|
12
|
+
execSync("tput cols", {
|
|
13
|
+
encoding: "utf-8",
|
|
14
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
15
|
+
}).trim(),
|
|
16
|
+
)
|
|
17
|
+
const rows = parseInt(
|
|
18
|
+
execSync("tput lines", {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
21
|
+
}).trim(),
|
|
22
|
+
)
|
|
23
|
+
if (cols > 0 && rows > 0) return { cols, rows }
|
|
24
|
+
} catch {}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (process.stdout.isTTY && process.stdout.columns === 0) {
|
|
29
|
+
const size = getTerminalSize()
|
|
30
|
+
if (size) {
|
|
31
|
+
Object.defineProperty(process.stdout, "columns", {
|
|
32
|
+
value: size.cols,
|
|
33
|
+
writable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
})
|
|
36
|
+
Object.defineProperty(process.stdout, "rows", {
|
|
37
|
+
value: size.rows,
|
|
38
|
+
writable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Keep patched values updated on terminal resize
|
|
43
|
+
process.on("SIGWINCH", () => {
|
|
44
|
+
const newSize = getTerminalSize()
|
|
45
|
+
if (newSize) {
|
|
46
|
+
process.stdout.columns = newSize.cols
|
|
47
|
+
process.stdout.rows = newSize.rows
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Test for ReviewAppView rendering with example YAML data
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, it } from "bun:test"
|
|
4
|
-
import { testRender } from "@
|
|
4
|
+
import { testRender } from "@opentuah/react/test-utils"
|
|
5
5
|
import { ReviewAppView } from "./review-app.tsx"
|
|
6
6
|
import { createHunk } from "./hunk-parser.ts"
|
|
7
7
|
import type { ReviewYaml } from "./types.ts"
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Supports live generation updates, theme switching, and resume from saved reviews.
|
|
4
4
|
|
|
5
5
|
import * as React from "react"
|
|
6
|
-
import { useKeyboard, useRenderer, useTerminalDimensions } from "@
|
|
7
|
-
import { MacOSScrollAccel, SyntaxStyle, BoxRenderable, CodeRenderable, TextRenderable, ScrollBoxRenderable } from "@
|
|
6
|
+
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentuah/react"
|
|
7
|
+
import { MacOSScrollAccel, SyntaxStyle, BoxRenderable, CodeRenderable, TextRenderable, ScrollBoxRenderable } from "@opentuah/core"
|
|
8
8
|
import { useCopySelection } from "../hooks/use-copy-selection.ts"
|
|
9
9
|
import type { Token } from "marked"
|
|
10
10
|
import { getResolvedTheme, getSyntaxTheme, defaultThemeName, themeNames, rgbaToHex } from "../themes.ts"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tests for StreamDisplay component
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, it } from "bun:test"
|
|
4
|
-
import { testRender } from "@
|
|
4
|
+
import { testRender } from "@opentuah/react/test-utils"
|
|
5
5
|
import type { SessionNotification } from "@agentclientprotocol/sdk"
|
|
6
6
|
import { StreamDisplay } from "./stream-display.tsx"
|
|
7
7
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Shows formatted tool operations with file names and edit statistics.
|
|
4
4
|
|
|
5
5
|
import * as React from "react"
|
|
6
|
-
import { SyntaxStyle } from "@
|
|
6
|
+
import { SyntaxStyle } from "@opentuah/core"
|
|
7
7
|
import type { SessionNotification } from "@agentclientprotocol/sdk"
|
|
8
8
|
import { formatNotifications, SYMBOLS, COLORS, isEditTool, type StreamLine } from "./acp-stream-display.ts"
|
|
9
9
|
import { getSyntaxTheme, getResolvedTheme, rgbaToHex } from "../themes.ts"
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
// Integration tests for --stdin pager mode (lazygit integration).
|
|
2
|
+
// Reproduces https://github.com/remorses/critique/issues/25
|
|
3
|
+
//
|
|
4
|
+
// Uses tuistory to launch critique in a PTY (exactly like lazygit does),
|
|
5
|
+
// pipes a real diff via stdin, and verifies the output is plain scrollback
|
|
6
|
+
// text — not interactive TUI escape sequences.
|
|
7
|
+
//
|
|
8
|
+
// tuistory spawns a PTY where isTTY=true, which is exactly how lazygit
|
|
9
|
+
// runs its pager (via github.com/creack/pty). This makes the test
|
|
10
|
+
// realistic: it catches the original bug where --stdin + TTY incorrectly
|
|
11
|
+
// entered interactive TUI mode instead of scrollback mode.
|
|
12
|
+
|
|
13
|
+
import { describe, test, expect, afterAll, beforeAll } from "bun:test"
|
|
14
|
+
import { launchTerminal } from "tuistory"
|
|
15
|
+
import fs from "fs"
|
|
16
|
+
import path from "path"
|
|
17
|
+
|
|
18
|
+
const TEMP_DIR = path.join(import.meta.dir, ".test-stdin-pager-tmp")
|
|
19
|
+
|
|
20
|
+
function tempFile(name: string, content: string): string {
|
|
21
|
+
const p = path.join(TEMP_DIR, name)
|
|
22
|
+
fs.writeFileSync(p, content)
|
|
23
|
+
return p
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function launchCritique(diffPath: string, opts?: { cols?: number; rows?: number }) {
|
|
27
|
+
return launchTerminal({
|
|
28
|
+
command: "bash",
|
|
29
|
+
args: ["-c", `cat "${diffPath}" | bun run src/cli.tsx --stdin`],
|
|
30
|
+
cols: opts?.cols ?? 100,
|
|
31
|
+
rows: opts?.rows ?? 30,
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
env: {
|
|
34
|
+
PATH: process.env.PATH,
|
|
35
|
+
HOME: process.env.HOME,
|
|
36
|
+
TERM: "xterm-256color",
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// -- Sample diffs --
|
|
42
|
+
|
|
43
|
+
const SINGLE_FILE_DIFF = [
|
|
44
|
+
"diff --git a/src/hello.ts b/src/hello.ts",
|
|
45
|
+
"--- a/src/hello.ts",
|
|
46
|
+
"+++ b/src/hello.ts",
|
|
47
|
+
"@@ -1,3 +1,3 @@",
|
|
48
|
+
" const greeting = 'hello'",
|
|
49
|
+
"-console.log(greeting)",
|
|
50
|
+
"+console.log(greeting + ' world')",
|
|
51
|
+
" export default greeting",
|
|
52
|
+
].join("\n")
|
|
53
|
+
|
|
54
|
+
// Empty patch — lazygit sends this when there are no changes for a file
|
|
55
|
+
const EMPTY_DIFF = ""
|
|
56
|
+
|
|
57
|
+
// Diff with only context lines and no actual changes (can happen with -U999)
|
|
58
|
+
const CONTEXT_ONLY_DIFF = [
|
|
59
|
+
"diff --git a/readme.md b/readme.md",
|
|
60
|
+
"--- a/readme.md",
|
|
61
|
+
"+++ b/readme.md",
|
|
62
|
+
"@@ -1,3 +1,3 @@",
|
|
63
|
+
" # My Project",
|
|
64
|
+
" ",
|
|
65
|
+
" Some description",
|
|
66
|
+
].join("\n")
|
|
67
|
+
|
|
68
|
+
// Multiple files in a single diff
|
|
69
|
+
const MULTI_FILE_DIFF = [
|
|
70
|
+
"diff --git a/src/index.ts b/src/index.ts",
|
|
71
|
+
"--- a/src/index.ts",
|
|
72
|
+
"+++ b/src/index.ts",
|
|
73
|
+
"@@ -1,4 +1,6 @@",
|
|
74
|
+
" import { App } from './app'",
|
|
75
|
+
"+import { Logger } from './logger'",
|
|
76
|
+
" ",
|
|
77
|
+
" const app = new App()",
|
|
78
|
+
"+const logger = new Logger()",
|
|
79
|
+
" app.start()",
|
|
80
|
+
"diff --git a/src/logger.ts b/src/logger.ts",
|
|
81
|
+
"new file mode 100644",
|
|
82
|
+
"--- /dev/null",
|
|
83
|
+
"+++ b/src/logger.ts",
|
|
84
|
+
"@@ -0,0 +1,5 @@",
|
|
85
|
+
"+export class Logger {",
|
|
86
|
+
"+ log(msg: string) {",
|
|
87
|
+
"+ console.log(`[LOG] ${msg}`)",
|
|
88
|
+
"+ }",
|
|
89
|
+
"+}",
|
|
90
|
+
].join("\n")
|
|
91
|
+
|
|
92
|
+
// Deletion-only diff
|
|
93
|
+
const DELETE_ONLY_DIFF = [
|
|
94
|
+
"diff --git a/src/deprecated.ts b/src/deprecated.ts",
|
|
95
|
+
"deleted file mode 100644",
|
|
96
|
+
"--- a/src/deprecated.ts",
|
|
97
|
+
"+++ /dev/null",
|
|
98
|
+
"@@ -1,4 +0,0 @@",
|
|
99
|
+
"-// This module is no longer needed",
|
|
100
|
+
"-export function oldHelper() {",
|
|
101
|
+
"- return 'deprecated'",
|
|
102
|
+
"-}",
|
|
103
|
+
].join("\n")
|
|
104
|
+
|
|
105
|
+
// Addition-only diff (new file)
|
|
106
|
+
const NEW_FILE_DIFF = [
|
|
107
|
+
"diff --git a/src/utils.ts b/src/utils.ts",
|
|
108
|
+
"new file mode 100644",
|
|
109
|
+
"--- /dev/null",
|
|
110
|
+
"+++ b/src/utils.ts",
|
|
111
|
+
"@@ -0,0 +1,7 @@",
|
|
112
|
+
"+export function clamp(value: number, min: number, max: number): number {",
|
|
113
|
+
"+ return Math.min(Math.max(value, min), max)",
|
|
114
|
+
"+}",
|
|
115
|
+
"+",
|
|
116
|
+
"+export function identity<T>(x: T): T {",
|
|
117
|
+
"+ return x",
|
|
118
|
+
"+}",
|
|
119
|
+
].join("\n")
|
|
120
|
+
|
|
121
|
+
// Large hunk with many changes
|
|
122
|
+
const LARGE_HUNK_DIFF = [
|
|
123
|
+
"diff --git a/config.json b/config.json",
|
|
124
|
+
"--- a/config.json",
|
|
125
|
+
"+++ b/config.json",
|
|
126
|
+
"@@ -1,9 +1,11 @@",
|
|
127
|
+
" {",
|
|
128
|
+
'- "name": "my-app",',
|
|
129
|
+
'+ "name": "my-awesome-app",',
|
|
130
|
+
'- "version": "1.0.0",',
|
|
131
|
+
'+ "version": "2.0.0",',
|
|
132
|
+
' "description": "A sample app",',
|
|
133
|
+
'- "main": "index.js",',
|
|
134
|
+
'+ "main": "dist/index.js",',
|
|
135
|
+
'+ "types": "dist/index.d.ts",',
|
|
136
|
+
' "scripts": {',
|
|
137
|
+
'- "build": "tsc"',
|
|
138
|
+
'+ "build": "tsc --project tsconfig.build.json",',
|
|
139
|
+
'+ "test": "bun test"',
|
|
140
|
+
" }",
|
|
141
|
+
" }",
|
|
142
|
+
].join("\n")
|
|
143
|
+
|
|
144
|
+
// Rename diff (common in lazygit)
|
|
145
|
+
const RENAME_DIFF = [
|
|
146
|
+
"diff --git a/src/old-name.ts b/src/new-name.ts",
|
|
147
|
+
"similarity index 90%",
|
|
148
|
+
"rename from src/old-name.ts",
|
|
149
|
+
"rename to src/new-name.ts",
|
|
150
|
+
"--- a/src/old-name.ts",
|
|
151
|
+
"+++ b/src/new-name.ts",
|
|
152
|
+
"@@ -1,3 +1,3 @@",
|
|
153
|
+
"-export const name = 'old'",
|
|
154
|
+
"+export const name = 'new'",
|
|
155
|
+
" export const version = 1",
|
|
156
|
+
" export default name",
|
|
157
|
+
].join("\n")
|
|
158
|
+
|
|
159
|
+
// Binary file diff (lazygit shows these)
|
|
160
|
+
const BINARY_DIFF = [
|
|
161
|
+
"diff --git a/logo.png b/logo.png",
|
|
162
|
+
"new file mode 100644",
|
|
163
|
+
"Binary files /dev/null and b/logo.png differ",
|
|
164
|
+
].join("\n")
|
|
165
|
+
|
|
166
|
+
describe("--stdin pager mode (lazygit issue #25)", () => {
|
|
167
|
+
beforeAll(() => {
|
|
168
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true })
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
afterAll(() => {
|
|
172
|
+
try {
|
|
173
|
+
fs.rmSync(TEMP_DIR, { recursive: true })
|
|
174
|
+
} catch {}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test("single file change", async () => {
|
|
178
|
+
const diffPath = tempFile("single.diff", SINGLE_FILE_DIFF)
|
|
179
|
+
const session = await launchCritique(diffPath)
|
|
180
|
+
await session.waitForText("hello", { timeout: 15000 })
|
|
181
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
182
|
+
|
|
183
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
184
|
+
"
|
|
185
|
+
a/src/hello.ts → b/src/hello.ts +1-1
|
|
186
|
+
|
|
187
|
+
1 const greeting = 'hello'
|
|
188
|
+
2 - console.log(greeting)
|
|
189
|
+
2 + console.log(greeting + ' world')
|
|
190
|
+
3 export default greeting"
|
|
191
|
+
`)
|
|
192
|
+
|
|
193
|
+
const lines = trimmed.split("\n").filter((l) => l.trim().length > 0)
|
|
194
|
+
expect(lines.length).toBeGreaterThan(0)
|
|
195
|
+
expect(lines.length).toBeLessThan(25)
|
|
196
|
+
session.close()
|
|
197
|
+
}, 30000)
|
|
198
|
+
|
|
199
|
+
test("empty diff produces no crash", async () => {
|
|
200
|
+
const diffPath = tempFile("empty.diff", EMPTY_DIFF)
|
|
201
|
+
const session = await launchCritique(diffPath)
|
|
202
|
+
|
|
203
|
+
// Empty diff should cause critique to exit quickly.
|
|
204
|
+
// Wait a bit for it to process and exit.
|
|
205
|
+
await new Promise((r) => setTimeout(r, 5000))
|
|
206
|
+
const trimmed = await session.text({ trimEnd: true, immediate: true })
|
|
207
|
+
|
|
208
|
+
// Should either be empty or show an error message — not a TUI
|
|
209
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
210
|
+
"
|
|
211
|
+
No changes to display"
|
|
212
|
+
`)
|
|
213
|
+
|
|
214
|
+
session.close()
|
|
215
|
+
}, 15000)
|
|
216
|
+
|
|
217
|
+
test("context-only diff (no actual changes)", async () => {
|
|
218
|
+
const diffPath = tempFile("context-only.diff", CONTEXT_ONLY_DIFF)
|
|
219
|
+
const session = await launchCritique(diffPath)
|
|
220
|
+
|
|
221
|
+
await new Promise((r) => setTimeout(r, 5000))
|
|
222
|
+
const trimmed = await session.text({ trimEnd: true, immediate: true })
|
|
223
|
+
|
|
224
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
225
|
+
"
|
|
226
|
+
a/readme.md → b/readme.md +0-0
|
|
227
|
+
|
|
228
|
+
1 # My Project
|
|
229
|
+
2
|
|
230
|
+
3 Some description"
|
|
231
|
+
`)
|
|
232
|
+
|
|
233
|
+
session.close()
|
|
234
|
+
}, 15000)
|
|
235
|
+
|
|
236
|
+
test("multiple files in one diff", async () => {
|
|
237
|
+
const diffPath = tempFile("multi.diff", MULTI_FILE_DIFF)
|
|
238
|
+
const session = await launchCritique(diffPath)
|
|
239
|
+
await session.waitForText("Logger", { timeout: 15000 })
|
|
240
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
241
|
+
|
|
242
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
243
|
+
"
|
|
244
|
+
b/src/logger.ts +5-0
|
|
245
|
+
|
|
246
|
+
1 + export class Logger {
|
|
247
|
+
2 + log(msg: string) {
|
|
248
|
+
3 + console.log(\`[LOG] \${msg}\`)
|
|
249
|
+
4 + }
|
|
250
|
+
5 + }
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
a/src/index.ts → b/src/index.ts +2-0
|
|
254
|
+
|
|
255
|
+
1 import { App } from './app'
|
|
256
|
+
2 + import { Logger } from './logger'
|
|
257
|
+
3
|
|
258
|
+
4 const app = new App()
|
|
259
|
+
5 + const logger = new Logger()
|
|
260
|
+
6 app.start()"
|
|
261
|
+
`)
|
|
262
|
+
|
|
263
|
+
// Should contain both filenames
|
|
264
|
+
expect(trimmed).toContain("index.ts")
|
|
265
|
+
expect(trimmed).toContain("logger.ts")
|
|
266
|
+
|
|
267
|
+
// Should not show the privacy notice
|
|
268
|
+
expect(trimmed).not.toContain("URL is private")
|
|
269
|
+
|
|
270
|
+
const lines = trimmed.split("\n").filter((l) => l.trim().length > 0)
|
|
271
|
+
expect(lines.length).toBeLessThan(40)
|
|
272
|
+
session.close()
|
|
273
|
+
}, 30000)
|
|
274
|
+
|
|
275
|
+
test("deleted file", async () => {
|
|
276
|
+
const diffPath = tempFile("delete.diff", DELETE_ONLY_DIFF)
|
|
277
|
+
const session = await launchCritique(diffPath)
|
|
278
|
+
await session.waitForText("deprecated", { timeout: 15000 })
|
|
279
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
280
|
+
|
|
281
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
282
|
+
"
|
|
283
|
+
a/src/deprecated.ts +0-4
|
|
284
|
+
|
|
285
|
+
1 - // This module is no longer needed
|
|
286
|
+
2 - export function oldHelper() {
|
|
287
|
+
3 - return 'deprecated'
|
|
288
|
+
4 - }"
|
|
289
|
+
`)
|
|
290
|
+
|
|
291
|
+
expect(trimmed).toContain("deprecated")
|
|
292
|
+
expect(trimmed).not.toContain("URL is private")
|
|
293
|
+
session.close()
|
|
294
|
+
}, 30000)
|
|
295
|
+
|
|
296
|
+
test("new file", async () => {
|
|
297
|
+
const diffPath = tempFile("newfile.diff", NEW_FILE_DIFF)
|
|
298
|
+
const session = await launchCritique(diffPath)
|
|
299
|
+
await session.waitForText("clamp", { timeout: 15000 })
|
|
300
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
301
|
+
|
|
302
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
303
|
+
"
|
|
304
|
+
b/src/utils.ts +7-0
|
|
305
|
+
|
|
306
|
+
1 + export function clamp(value: number, min: number, max: number): number {
|
|
307
|
+
2 + return Math.min(Math.max(value, min), max)
|
|
308
|
+
3 + }
|
|
309
|
+
4 +
|
|
310
|
+
5 + export function identity<T>(x: T): T {
|
|
311
|
+
6 + return x
|
|
312
|
+
7 + }"
|
|
313
|
+
`)
|
|
314
|
+
|
|
315
|
+
expect(trimmed).toContain("clamp")
|
|
316
|
+
expect(trimmed).toContain("identity")
|
|
317
|
+
expect(trimmed).not.toContain("URL is private")
|
|
318
|
+
session.close()
|
|
319
|
+
}, 30000)
|
|
320
|
+
|
|
321
|
+
test("large hunk with many changes", async () => {
|
|
322
|
+
const diffPath = tempFile("large.diff", LARGE_HUNK_DIFF)
|
|
323
|
+
const session = await launchCritique(diffPath)
|
|
324
|
+
await session.waitForText("config.json", { timeout: 15000 })
|
|
325
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
326
|
+
|
|
327
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
328
|
+
"
|
|
329
|
+
a/config.json → b/config.json +6-4
|
|
330
|
+
|
|
331
|
+
1 {
|
|
332
|
+
2 - "name": "my-app",
|
|
333
|
+
3 - "version": "1.0.0",
|
|
334
|
+
2 + "name": "my-awesome-app",
|
|
335
|
+
3 + "version": "2.0.0",
|
|
336
|
+
4 "description": "A sample app",
|
|
337
|
+
5 - "main": "index.js",
|
|
338
|
+
5 + "main": "dist/index.js",
|
|
339
|
+
6 + "types": "dist/index.d.ts",
|
|
340
|
+
7 "scripts": {
|
|
341
|
+
7 - "build": "tsc"
|
|
342
|
+
8 + "build": "tsc --project tsconfig.build.json",
|
|
343
|
+
9 + "test": "bun test"
|
|
344
|
+
10 }
|
|
345
|
+
11 }"
|
|
346
|
+
`)
|
|
347
|
+
|
|
348
|
+
expect(trimmed).toContain("config.json")
|
|
349
|
+
expect(trimmed).toContain("my-awesome-app")
|
|
350
|
+
expect(trimmed).not.toContain("URL is private")
|
|
351
|
+
session.close()
|
|
352
|
+
}, 30000)
|
|
353
|
+
|
|
354
|
+
test("file rename with changes", async () => {
|
|
355
|
+
const diffPath = tempFile("rename.diff", RENAME_DIFF)
|
|
356
|
+
const session = await launchCritique(diffPath)
|
|
357
|
+
await session.waitForText("new-name", { timeout: 15000 })
|
|
358
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
359
|
+
|
|
360
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
361
|
+
"
|
|
362
|
+
src/old-name.ts → src/new-name.ts +1-1
|
|
363
|
+
|
|
364
|
+
1 - export const name = 'old'
|
|
365
|
+
1 + export const name = 'new'
|
|
366
|
+
2 export const version = 1
|
|
367
|
+
3 export default name"
|
|
368
|
+
`)
|
|
369
|
+
|
|
370
|
+
// Should show the rename
|
|
371
|
+
expect(trimmed).toContain("old-name")
|
|
372
|
+
expect(trimmed).toContain("new-name")
|
|
373
|
+
expect(trimmed).not.toContain("URL is private")
|
|
374
|
+
session.close()
|
|
375
|
+
}, 30000)
|
|
376
|
+
|
|
377
|
+
test("binary file diff", async () => {
|
|
378
|
+
const diffPath = tempFile("binary.diff", BINARY_DIFF)
|
|
379
|
+
const session = await launchCritique(diffPath)
|
|
380
|
+
|
|
381
|
+
// Binary diffs may not render content — wait for exit or timeout
|
|
382
|
+
await new Promise((r) => setTimeout(r, 5000))
|
|
383
|
+
const trimmed = await session.text({ trimEnd: true, immediate: true })
|
|
384
|
+
|
|
385
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
386
|
+
"
|
|
387
|
+
unknown +0-0"
|
|
388
|
+
`)
|
|
389
|
+
|
|
390
|
+
expect(trimmed).not.toContain("URL is private")
|
|
391
|
+
session.close()
|
|
392
|
+
}, 15000)
|
|
393
|
+
|
|
394
|
+
test("narrow terminal (40 cols) forces unified view", async () => {
|
|
395
|
+
const diffPath = tempFile("narrow.diff", SINGLE_FILE_DIFF)
|
|
396
|
+
const session = await launchCritique(diffPath, { cols: 40 })
|
|
397
|
+
await session.waitForText("hello", { timeout: 15000 })
|
|
398
|
+
const trimmed = await session.text({ trimEnd: true })
|
|
399
|
+
|
|
400
|
+
expect(trimmed).toMatchInlineSnapshot(`
|
|
401
|
+
"
|
|
402
|
+
a/src/hello.ts → b/src/hello.ts +1-1
|
|
403
|
+
|
|
404
|
+
1 const greeting = 'hello'
|
|
405
|
+
2 - console.log(greeting)
|
|
406
|
+
2 + console.log(greeting + ' world')
|
|
407
|
+
3 export default greeting"
|
|
408
|
+
`)
|
|
409
|
+
|
|
410
|
+
expect(trimmed).toContain("hello")
|
|
411
|
+
expect(trimmed).not.toContain("URL is private")
|
|
412
|
+
|
|
413
|
+
// Every non-empty line should fit within 40 cols
|
|
414
|
+
const lines = trimmed.split("\n").filter((l) => l.trim().length > 0)
|
|
415
|
+
for (const line of lines) {
|
|
416
|
+
expect(line.length).toBeLessThanOrEqual(40)
|
|
417
|
+
}
|
|
418
|
+
session.close()
|
|
419
|
+
}, 30000)
|
|
420
|
+
})
|
package/src/themes.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Loads JSON theme files lazily on demand, resolves color references,
|
|
3
3
|
// and provides both UI colors and Tree-sitter compatible syntax styles.
|
|
4
4
|
|
|
5
|
-
import { parseColor, RGBA } from "@
|
|
5
|
+
import { parseColor, RGBA } from "@opentuah/core";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
8
|
// Only import the default theme statically for fast startup
|
package/src/web-utils.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, test, expect, afterEach } from "bun:test"
|
|
2
|
-
import { createTestRenderer } from "@
|
|
3
|
-
import { createRoot } from "@
|
|
2
|
+
import { createTestRenderer } from "@opentuah/core/testing"
|
|
3
|
+
import { createRoot } from "@opentuah/react"
|
|
4
4
|
import React from "react"
|
|
5
|
-
import { RGBA } from "@
|
|
5
|
+
import { RGBA } from "@opentuah/core"
|
|
6
6
|
|
|
7
7
|
describe("getSpanLines rendering", () => {
|
|
8
8
|
let renderer: Awaited<ReturnType<typeof createTestRenderer>>["renderer"] | null = null
|
package/src/web-utils.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import fs from "fs"
|
|
|
8
8
|
import { tmpdir } from "os"
|
|
9
9
|
import { join } from "path"
|
|
10
10
|
import { getResolvedTheme, rgbaToHex } from "./themes.ts"
|
|
11
|
-
import type { CapturedFrame, RootRenderable, CliRenderer } from "@
|
|
11
|
+
import type { CapturedFrame, RootRenderable, CliRenderer } from "@opentuah/core"
|
|
12
12
|
import type { IndexedHunk, ReviewYaml } from "./review/types.ts"
|
|
13
13
|
import { loadStoredLicenseKey, loadOrCreateOwnerSecret } from "./license.ts"
|
|
14
14
|
|
|
@@ -24,7 +24,7 @@ export interface CaptureOptions {
|
|
|
24
24
|
title?: string
|
|
25
25
|
/** Wrap mode for long lines (default: "word") */
|
|
26
26
|
wrapMode?: "word" | "char" | "none"
|
|
27
|
-
/** Show privacy/expiry notice block at top (default:
|
|
27
|
+
/** Show privacy/expiry notice block at top (default: false, enabled for web uploads) */
|
|
28
28
|
showNotice?: boolean
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -118,9 +118,9 @@ export async function renderDiffToFrame(
|
|
|
118
118
|
diffContent: string,
|
|
119
119
|
options: CaptureOptions
|
|
120
120
|
): Promise<CapturedFrame> {
|
|
121
|
-
const { createTestRenderer } = await import("@
|
|
122
|
-
const { createRoot } = await import("@
|
|
123
|
-
const { getTreeSitterClient } = await import("@
|
|
121
|
+
const { createTestRenderer } = await import("@opentuah/core/testing")
|
|
122
|
+
const { createRoot } = await import("@opentuah/react")
|
|
123
|
+
const { getTreeSitterClient } = await import("@opentuah/core")
|
|
124
124
|
const React = await import("react")
|
|
125
125
|
const { parsePatch, formatPatch } = await import("diff")
|
|
126
126
|
|
|
@@ -160,7 +160,7 @@ export async function renderDiffToFrame(
|
|
|
160
160
|
const webMuted = rgbaToHex(webTheme.textMuted)
|
|
161
161
|
|
|
162
162
|
const showExpiryNotice = shouldShowExpiryNotice()
|
|
163
|
-
const showNotice = options.showNotice
|
|
163
|
+
const showNotice = options.showNotice === true
|
|
164
164
|
|
|
165
165
|
// Create the diff view component
|
|
166
166
|
// NOTE: No height: "100%" - let content determine its natural height
|
|
@@ -293,8 +293,8 @@ export async function captureToHtml(
|
|
|
293
293
|
): Promise<string> {
|
|
294
294
|
const { frameToHtmlDocument } = await import("./ansi-html.ts")
|
|
295
295
|
|
|
296
|
-
// Render diff to captured frame
|
|
297
|
-
const frame = await renderDiffToFrame(diffContent, options)
|
|
296
|
+
// Render diff to captured frame (with notice for web uploads)
|
|
297
|
+
const frame = await renderDiffToFrame(diffContent, { ...options, showNotice: true })
|
|
298
298
|
|
|
299
299
|
// Get theme colors for HTML output
|
|
300
300
|
const theme = getResolvedTheme(options.themeName)
|
|
@@ -375,9 +375,9 @@ export interface ReviewRenderOptions extends CaptureOptions {
|
|
|
375
375
|
export async function renderReviewToFrame(
|
|
376
376
|
options: ReviewRenderOptions
|
|
377
377
|
): Promise<CapturedFrame> {
|
|
378
|
-
const { createTestRenderer } = await import("@
|
|
379
|
-
const { createRoot } = await import("@
|
|
380
|
-
const { getTreeSitterClient } = await import("@
|
|
378
|
+
const { createTestRenderer } = await import("@opentuah/core/testing")
|
|
379
|
+
const { createRoot } = await import("@opentuah/react")
|
|
380
|
+
const { getTreeSitterClient } = await import("@opentuah/core")
|
|
381
381
|
const React = await import("react")
|
|
382
382
|
|
|
383
383
|
// Pre-initialize TreeSitter client to ensure syntax highlighting works
|
|
@@ -396,7 +396,7 @@ export async function renderReviewToFrame(
|
|
|
396
396
|
const webText = rgbaToHex(theme.text)
|
|
397
397
|
const webMuted = rgbaToHex(theme.textMuted)
|
|
398
398
|
const showExpiryNotice = shouldShowExpiryNotice()
|
|
399
|
-
const showNotice = options.showNotice
|
|
399
|
+
const showNotice = options.showNotice === true
|
|
400
400
|
|
|
401
401
|
// Content-fitting: start small, double if clipped, shrink to fit
|
|
402
402
|
let currentHeight = 100
|
|
@@ -488,8 +488,8 @@ export async function captureReviewToHtml(
|
|
|
488
488
|
): Promise<string> {
|
|
489
489
|
const { frameToHtmlDocument } = await import("./ansi-html.ts")
|
|
490
490
|
|
|
491
|
-
// Render review to captured frame
|
|
492
|
-
const frame = await renderReviewToFrame(options)
|
|
491
|
+
// Render review to captured frame (with notice for web uploads)
|
|
492
|
+
const frame = await renderReviewToFrame({ ...options, showNotice: true })
|
|
493
493
|
|
|
494
494
|
// Get theme colors for HTML output
|
|
495
495
|
const theme = getResolvedTheme(options.themeName)
|