critique 0.1.3 → 0.1.6

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,26 @@
1
+ # 0.1.6
2
+
3
+ - Web preview:
4
+ - Fix horizontal centering using flexbox on body instead of margin auto
5
+
6
+ # 0.1.5
7
+
8
+ - Web preview:
9
+ - Switch to `ghostty-opentui` for unlimited scrollback (fixes truncation on large diffs)
10
+ - Dynamically calculate rows from diff content instead of hardcoded limit
11
+ - Add `--open` flag to open browser (disabled by default)
12
+ - Center container horizontally with `max-width: 100vw` (fixes iPad overflow)
13
+ - Set line background color to reduce Safari content-visibility flicker
14
+ - Reduce min font size from 8px to 4px for better mobile fit
15
+ - Fix JSX syntax error (`treeSitterClient={undefined}`)
16
+
17
+ # 0.1.4
18
+
19
+ - Web preview:
20
+ - Fix syntax highlighting not appearing in web output
21
+ - Allow multiple renders before capturing (syntax highlighting is async)
22
+ - Use debounced exit (300ms after last render) instead of blocking re-renders
23
+
1
24
  # 0.1.3
2
25
 
3
26
  - Themes:
package/bun.lock CHANGED
@@ -13,7 +13,7 @@
13
13
  "@xmorse/bun-pty": "0.4.0",
14
14
  "cac": "^6.7.14",
15
15
  "diff": "^8.0.2",
16
- "opentui-ansi-vt": "1.2.11",
16
+ "ghostty-opentui": "^1.3.11",
17
17
  "react": "^19.2.0",
18
18
  "react-error-boundary": "^6.0.0",
19
19
  "shiki": "^3.20.0",
@@ -374,6 +374,8 @@
374
374
 
375
375
  "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
376
376
 
377
+ "ghostty-opentui": ["ghostty-opentui@1.3.11", "", { "dependencies": { "strip-ansi": "^7.1.2" }, "peerDependencies": { "@opentui/core": "*" }, "optionalPeers": ["@opentui/core"] }, "sha512-taKOhQD65dip/GBi2eDicyS6Z+m7T6CAWyUcFUVP3nX+JKTCiOwfncGqhnqtSa8VE3mG6VHaPzIimDVN7pdD6w=="],
378
+
377
379
  "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
378
380
 
379
381
  "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
@@ -432,8 +434,6 @@
432
434
 
433
435
  "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="],
434
436
 
435
- "opentui-ansi-vt": ["opentui-ansi-vt@1.2.11", "", { "dependencies": { "strip-ansi": "^7.1.2" }, "peerDependencies": { "@opentui/core": "*" }, "optionalPeers": ["@opentui/core"] }, "sha512-T+uxxisV2m8LaYgnzvHqnJX6gLJZ5UrvMVpEwjC4ocxKP2DtY++//IRvVDo1db9c2nT3Sz8H/sw22X9CXW/Igg=="],
436
-
437
437
  "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
438
438
 
439
439
  "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
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.3",
5
+ "version": "0.1.6",
6
6
  "private": false,
7
7
  "bin": "./src/cli.tsx",
8
8
  "scripts": {
@@ -30,7 +30,7 @@
30
30
  "@xmorse/bun-pty": "0.4.0",
31
31
  "cac": "^6.7.14",
32
32
  "diff": "^8.0.2",
33
- "opentui-ansi-vt": "1.2.11",
33
+ "ghostty-opentui": "^1.3.11",
34
34
  "react": "^19.2.0",
35
35
  "react-error-boundary": "^6.0.0",
36
36
  "shiki": "^3.20.0",
package/src/ansi-html.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ptyToJson, StyleFlags, type TerminalData, type TerminalLine, type TerminalSpan } from "opentui-ansi-vt"
1
+ import { ptyToJson, StyleFlags, type TerminalData, type TerminalLine, type TerminalSpan } from "ghostty-opentui"
2
2
 
3
3
  export interface AnsiToHtmlOptions {
4
4
  cols?: number
@@ -147,21 +147,30 @@ html {
147
147
  }
148
148
  html, body {
149
149
  height: 100%;
150
+ max-width: 100vw;
151
+ overflow-x: hidden;
150
152
  background-color: ${backgroundColor};
151
153
  color: #c5c8c6;
152
154
  font-family: ${fontFamily};
153
155
  font-size: ${fontSize};
154
156
  line-height: 1.6;
155
157
  }
158
+ body {
159
+ display: flex;
160
+ justify-content: center;
161
+ }
156
162
  #content {
157
163
  padding: 16px;
158
164
  overflow-x: auto;
165
+ max-width: 100vw;
166
+ box-sizing: border-box;
159
167
  }
160
168
  .line {
161
169
  white-space: pre;
162
170
  display: flex;
163
171
  content-visibility: auto;
164
172
  contain-intrinsic-block-size: auto 1lh;
173
+ background-color: ${backgroundColor};
165
174
  }
166
175
  .line span {
167
176
  white-space: pre;
@@ -177,7 +186,7 @@ ${content}
177
186
  const cols = ${cols};
178
187
  const charRatio = ${charWidthRatio};
179
188
  const padding = ${padding};
180
- const minFontSize = 8;
189
+ const minFontSize = 4;
181
190
  const maxFontSize = 16;
182
191
 
183
192
  function adjustFontSize() {
package/src/cli.tsx CHANGED
@@ -225,6 +225,7 @@ function DiffView({ diff, view, filetype, themeName }: DiffViewProps) {
225
225
  <diff
226
226
  diff={diff}
227
227
  view={view}
228
+ treeSitterClient={undefined}
228
229
  filetype={filetype}
229
230
  syntaxStyle={syntaxStyle}
230
231
  showLineNumbers
@@ -998,8 +999,9 @@ cli
998
999
  "Number of columns for rendering (use ~100 for mobile)",
999
1000
  { default: 240 },
1000
1001
  )
1001
- .option("--rows <rows>", "Number of rows for rendering", { default: 2000 })
1002
- .option("--local", "Open local preview instead of uploading")
1002
+
1003
+ .option("--local", "Save local preview instead of uploading")
1004
+ .option("--open", "Open in browser after generating")
1003
1005
  .option("--context <lines>", "Number of context lines (default: 3)")
1004
1006
  .action(async (ref, options) => {
1005
1007
  const pty = await import("@xmorse/bun-pty");
@@ -1016,7 +1018,6 @@ cli
1016
1018
  })();
1017
1019
 
1018
1020
  const cols = parseInt(options.cols) || 240;
1019
- const rows = parseInt(options.rows) || 2000;
1020
1021
 
1021
1022
  console.log("Capturing diff output...");
1022
1023
 
@@ -1030,6 +1031,14 @@ cli
1030
1031
  process.exit(0);
1031
1032
  }
1032
1033
 
1034
+ // Calculate required rows from diff content
1035
+ const { parsePatch } = await import("diff");
1036
+ const files = parsePatch(gitDiff);
1037
+ const renderRows = files.reduce((sum, file) => {
1038
+ const diffLines = file.hunks.reduce((h, hunk) => h + hunk.lines.length, 0);
1039
+ return sum + diffLines + 5; // header + margin per file
1040
+ }, 100); // base padding
1041
+
1033
1042
  // Write diff to temp file
1034
1043
  const diffFile = join(tmpdir(), `critique-web-diff-${Date.now()}.patch`);
1035
1044
  fs.writeFileSync(diffFile, gitDiff);
@@ -1045,12 +1054,12 @@ cli
1045
1054
  "--cols",
1046
1055
  String(cols),
1047
1056
  "--rows",
1048
- String(rows),
1057
+ String(renderRows),
1049
1058
  ],
1050
1059
  {
1051
1060
  name: "xterm-256color",
1052
1061
  cols: cols,
1053
- rows: rows,
1062
+ rows: renderRows,
1054
1063
 
1055
1064
  cwd: process.cwd(),
1056
1065
  env: { ...process.env, TERM: "xterm-256color" } as Record<
@@ -1088,25 +1097,27 @@ cli
1088
1097
  }
1089
1098
 
1090
1099
  // Convert ANSI to HTML document
1091
- const html = ansiToHtmlDocument(ansiOutput, { cols, rows });
1100
+ const html = ansiToHtmlDocument(ansiOutput, { cols, rows: renderRows });
1092
1101
 
1093
1102
  if (options.local) {
1094
- // Save locally and open
1103
+ // Save locally
1095
1104
  const htmlFile = join(tmpdir(), `critique-${Date.now()}.html`);
1096
1105
  fs.writeFileSync(htmlFile, html);
1097
1106
  console.log(`Saved to: ${htmlFile}`);
1098
1107
 
1099
- // Try to open in browser
1100
- const openCmd =
1101
- process.platform === "darwin"
1102
- ? "open"
1103
- : process.platform === "win32"
1104
- ? "start"
1105
- : "xdg-open";
1106
- try {
1107
- await execAsync(`${openCmd} "${htmlFile}"`);
1108
- } catch {
1109
- console.log("Could not open browser automatically");
1108
+ // Open in browser if requested
1109
+ if (options.open) {
1110
+ const openCmd =
1111
+ process.platform === "darwin"
1112
+ ? "open"
1113
+ : process.platform === "win32"
1114
+ ? "start"
1115
+ : "xdg-open";
1116
+ try {
1117
+ await execAsync(`${openCmd} "${htmlFile}"`);
1118
+ } catch {
1119
+ console.log("Could not open browser automatically");
1120
+ }
1110
1121
  }
1111
1122
  process.exit(0);
1112
1123
  }
@@ -1132,17 +1143,19 @@ cli
1132
1143
  console.log(`\nPreview URL: ${result.url}`);
1133
1144
  console.log(`(expires in 7 days)`);
1134
1145
 
1135
- // Try to open in browser
1136
- const openCmd =
1137
- process.platform === "darwin"
1138
- ? "open"
1139
- : process.platform === "win32"
1140
- ? "start"
1141
- : "xdg-open";
1142
- try {
1143
- await execAsync(`${openCmd} "${result.url}"`);
1144
- } catch {
1145
- // Silent fail - user can copy URL
1146
+ // Open in browser if requested
1147
+ if (options.open) {
1148
+ const openCmd =
1149
+ process.platform === "darwin"
1150
+ ? "open"
1151
+ : process.platform === "win32"
1152
+ ? "start"
1153
+ : "xdg-open";
1154
+ try {
1155
+ await execAsync(`${openCmd} "${result.url}"`);
1156
+ } catch {
1157
+ // Silent fail - user can copy URL
1158
+ }
1146
1159
  }
1147
1160
  } catch (error: any) {
1148
1161
  console.error("Failed to upload:", error.message);
@@ -1164,7 +1177,7 @@ cli
1164
1177
  .option("--rows <rows>", "Terminal rows", { default: 1000 })
1165
1178
  .action(async (diffFile: string, options) => {
1166
1179
  const cols = parseInt(options.cols) || 120;
1167
- const rows = parseInt(options.rows) || 40;
1180
+ const rows = parseInt(options.rows) || 1000;
1168
1181
 
1169
1182
  const { parsePatch, formatPatch } = await import("diff");
1170
1183
 
@@ -1201,7 +1214,7 @@ cli
1201
1214
  process.exit(0);
1202
1215
  }
1203
1216
 
1204
- // Override terminal size
1217
+ // Override terminal size (rows calculated by caller from diff content)
1205
1218
  process.stdout.columns = cols;
1206
1219
  process.stdout.rows = rows;
1207
1220
 
@@ -1210,20 +1223,23 @@ cli
1210
1223
  useAlternateScreen: false,
1211
1224
  });
1212
1225
 
1213
- // Track if we've rendered once
1214
- let hasRendered = false;
1226
+ // Wait for syntax highlighting to complete (it's async)
1227
+ // Allow multiple renders, then exit after highlighting is ready
1228
+ let renderCount = 0;
1215
1229
  const originalRequestRender = renderer.root.requestRender.bind(
1216
1230
  renderer.root,
1217
1231
  );
1232
+ let exitTimeout: ReturnType<typeof setTimeout> | undefined;
1218
1233
  renderer.root.requestRender = function () {
1219
- if (hasRendered) return; // Skip subsequent renders
1220
- hasRendered = true;
1234
+ renderCount++;
1221
1235
  originalRequestRender();
1222
- // Exit after the first render completes
1223
- setTimeout(() => {
1236
+ // Reset timeout on each render - exit 1s after last render
1237
+ // Tree-sitter highlighting is async and can take time for multiple files
1238
+ if (exitTimeout) clearTimeout(exitTimeout);
1239
+ exitTimeout = setTimeout(() => {
1224
1240
  renderer.destroy();
1225
1241
  process.exit(0);
1226
- }, 100);
1242
+ }, 1000);
1227
1243
  };
1228
1244
 
1229
1245
  // Use unified diff for narrow viewports (mobile), split view for wider ones