critique 0.1.122 → 0.1.127

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/dist/cli.js CHANGED
@@ -909,7 +909,7 @@ async function runWebMode(diffContent, options) {
909
909
  // after the URL is printed, so the user sees the URL faster.
910
910
  const { htmlDesktop, htmlMobile, ogImage } = await captureResponsiveHtml(diffContent, { desktopCols, mobileCols, baseRows, themeName, title: options.title, skipOgImage: true });
911
911
  log("Uploading...");
912
- const result = await uploadHtml(htmlDesktop, htmlMobile);
912
+ const result = await uploadHtml(htmlDesktop, htmlMobile, undefined, diffContent);
913
913
  log(`\nPreview URL: ${result.url}`);
914
914
  log(formatPreviewExpiry(result.expiresInDays));
915
915
  if (typeof result.expiresInDays === "number") {
@@ -0,0 +1,16 @@
1
+ export type KvValueMetadata = {
2
+ contentType?: string;
3
+ contentEncoding?: "gzip";
4
+ schemaVersion?: 1;
5
+ };
6
+ export declare const KV_SCHEMA_VERSION: 1;
7
+ export declare function buildKvPutOptions(ttlSeconds?: number, metadata?: KvValueMetadata): {
8
+ expirationTtl?: number;
9
+ metadata?: KvValueMetadata;
10
+ } | undefined;
11
+ export declare function gzipArrayBuffer(buffer: ArrayBuffer): Promise<ArrayBuffer>;
12
+ export declare function gunzipArrayBuffer(buffer: ArrayBuffer): Promise<ArrayBuffer>;
13
+ export declare function gzipText(value: string): Promise<ArrayBuffer>;
14
+ export declare function decodeTextFromKv(value: ArrayBuffer, metadata: KvValueMetadata | null): Promise<string>;
15
+ export declare function decodeBinaryFromKv(value: ArrayBuffer, metadata: KvValueMetadata | null): Promise<ArrayBuffer>;
16
+ //# sourceMappingURL=kv-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv-codec.d.ts","sourceRoot":"","sources":["../src/kv-codec.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,CAAC,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAG,CAAU,CAAA;AAE3C,wBAAgB,iBAAiB,CAC/B,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,eAAe,GACzB;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAE,GAAG,SAAS,CASpE;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAG/E;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAGjF;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAGlE;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,eAAe,GAAG,IAAI,GAC/B,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,eAAe,GAAG,IAAI,GAC/B,OAAO,CAAC,WAAW,CAAC,CAKtB"}
@@ -0,0 +1,36 @@
1
+ // KV value compression/decompression helpers for Cloudflare Worker storage.
2
+ // Provides metadata-aware gzip encoding and decoding for text and binary payloads.
3
+ export const KV_SCHEMA_VERSION = 1;
4
+ export function buildKvPutOptions(ttlSeconds, metadata) {
5
+ if (ttlSeconds === undefined && metadata === undefined) {
6
+ return undefined;
7
+ }
8
+ return {
9
+ ...(ttlSeconds !== undefined ? { expirationTtl: ttlSeconds } : {}),
10
+ ...(metadata !== undefined ? { metadata } : {}),
11
+ };
12
+ }
13
+ export async function gzipArrayBuffer(buffer) {
14
+ const stream = new Blob([buffer]).stream().pipeThrough(new CompressionStream("gzip"));
15
+ return await new Response(stream).arrayBuffer();
16
+ }
17
+ export async function gunzipArrayBuffer(buffer) {
18
+ const stream = new Blob([buffer]).stream().pipeThrough(new DecompressionStream("gzip"));
19
+ return await new Response(stream).arrayBuffer();
20
+ }
21
+ export async function gzipText(value) {
22
+ const bytes = new TextEncoder().encode(value);
23
+ return gzipArrayBuffer(bytes.buffer);
24
+ }
25
+ export async function decodeTextFromKv(value, metadata) {
26
+ const buffer = metadata?.contentEncoding === "gzip"
27
+ ? await gunzipArrayBuffer(value)
28
+ : value;
29
+ return new TextDecoder().decode(buffer);
30
+ }
31
+ export async function decodeBinaryFromKv(value, metadata) {
32
+ if (metadata?.contentEncoding === "gzip") {
33
+ return await gunzipArrayBuffer(value);
34
+ }
35
+ return value;
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=kv-codec.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv-codec.test.d.ts","sourceRoot":"","sources":["../src/kv-codec.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ // Tests for KV gzip codec helpers used by the Cloudflare worker.
2
+ import { describe, expect, test } from "bun:test";
3
+ import { buildKvPutOptions, decodeBinaryFromKv, decodeTextFromKv, gzipArrayBuffer, gzipText, } from "./kv-codec.js";
4
+ describe("kv-codec", () => {
5
+ test("round-trips gzip text payloads", async () => {
6
+ const input = "<html><body>diff line +1 -0</body></html>\n".repeat(200);
7
+ const compressed = await gzipText(input);
8
+ const decoded = await decodeTextFromKv(compressed, {
9
+ contentType: "text/html; charset=utf-8",
10
+ contentEncoding: "gzip",
11
+ schemaVersion: 1,
12
+ });
13
+ expect(decoded).toBe(input);
14
+ });
15
+ test("decodes legacy uncompressed text payloads", async () => {
16
+ const input = "plain html without kv metadata";
17
+ const bytes = new TextEncoder().encode(input);
18
+ const decoded = await decodeTextFromKv(bytes.buffer, null);
19
+ expect(decoded).toBe(input);
20
+ });
21
+ test("round-trips gzip binary payloads", async () => {
22
+ const bytes = Uint8Array.from({ length: 1024 }, (_, index) => index % 256);
23
+ const compressed = await gzipArrayBuffer(bytes.buffer);
24
+ const decoded = await decodeBinaryFromKv(compressed, {
25
+ contentType: "image/png",
26
+ contentEncoding: "gzip",
27
+ schemaVersion: 1,
28
+ });
29
+ expect(new Uint8Array(decoded)).toEqual(bytes);
30
+ });
31
+ test("returns undefined put options when empty", () => {
32
+ expect(buildKvPutOptions()).toBeUndefined();
33
+ });
34
+ test("builds put options with ttl and metadata", () => {
35
+ expect(buildKvPutOptions(3600, {
36
+ contentType: "text/html; charset=utf-8",
37
+ contentEncoding: "gzip",
38
+ schemaVersion: 1,
39
+ })).toEqual({
40
+ expirationTtl: 3600,
41
+ metadata: {
42
+ contentType: "text/html; charset=utf-8",
43
+ contentEncoding: "gzip",
44
+ schemaVersion: 1,
45
+ },
46
+ });
47
+ });
48
+ });
@@ -39,6 +39,13 @@ export declare function slugifyFileName(name: string): string;
39
39
  * Returns null for non-diff lines (headers, hunk markers, etc.).
40
40
  */
41
41
  export declare function extractLineNumber(line: CapturedLine): string | null;
42
+ /**
43
+ * Match a rendered tree file row and extract the file path label.
44
+ * Examples:
45
+ * "│ ├── index.ts (+5,-2)"
46
+ * "└── README.md (-15)"
47
+ */
48
+ export declare function extractTreeFilePath(lineText: string): string | null;
42
49
  /**
43
50
  * Build line-indexed anchors from file section layout positions.
44
51
  * This avoids regex detection on rendered text, which can produce
@@ -110,7 +117,7 @@ export declare function captureReviewResponsiveHtml(options: {
110
117
  /**
111
118
  * Upload HTML to the critique.work worker
112
119
  */
113
- export declare function uploadHtml(htmlDesktop: string, htmlMobile: string, ogImage?: Buffer | null): Promise<UploadResult>;
120
+ export declare function uploadHtml(htmlDesktop: string, htmlMobile: string, ogImage?: Buffer | null, patch?: string): Promise<UploadResult>;
114
121
  /**
115
122
  * Upload OG image to an existing diff via PATCH.
116
123
  * Called in the background after the initial upload returns the URL.
@@ -1 +1 @@
1
- {"version":3,"file":"web-utils.d.ts","sourceRoot":"","sources":["../src/web-utils.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web-utils.d.ts","sourceRoot":"","sources":["../src/web-utils.tsx"],"names":[],"mappings":"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"}
package/dist/web-utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentuah/react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentuah/react/jsx-runtime";
2
2
  // Web preview generation utilities for uploading diffs to critique.work.
3
3
  // Renders diff components using opentui test renderer, converts to HTML with responsive layout,
4
4
  // and uploads desktop/mobile versions for shareable diff viewing.
@@ -8,6 +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.js";
11
+ import { buildDirectoryTree } from "./directory-tree.js";
11
12
  import { DiffRenderable } from "@opentuah/core";
12
13
  import { loadStoredLicenseKey, loadOrCreateOwnerSecret } from "./license.js";
13
14
  const execAsync = promisify(exec);
@@ -119,13 +120,12 @@ async function renderDiffToFrameWithSectionPositions(diffContent, options) {
119
120
  const { createTestRenderer } = await import("@opentuah/core/testing");
120
121
  const { createRoot } = await import("@opentuah/react");
121
122
  const { getTreeSitterClient } = await import("@opentuah/core");
122
- const React = await import("react");
123
123
  const { parsePatch, formatPatch } = await import("diff");
124
124
  // Pre-initialize TreeSitter client to ensure syntax highlighting works
125
125
  const tsClient = getTreeSitterClient();
126
126
  await tsClient.initialize();
127
- const { DiffView } = await import("./components/index.js");
128
- const { getFileName, getOldFileName, countChanges, getViewMode, processFiles, detectFiletype, stripSubmoduleHeaders, parseGitDiffFiles, } = await import("./diff-utils.js");
127
+ const { DiffView, DirectoryTreeView } = await import("./components/index.js");
128
+ const { getFileName, getOldFileName, countChanges, getFileStatus, getViewMode, processFiles, detectFiletype, stripSubmoduleHeaders, parseGitDiffFiles, } = await import("./diff-utils.js");
129
129
  const { themeNames, defaultThemeName } = await import("./themes.js");
130
130
  const themeName = options.themeName && themeNames.includes(options.themeName)
131
131
  ? options.themeName
@@ -134,6 +134,19 @@ async function renderDiffToFrameWithSectionPositions(diffContent, options) {
134
134
  const files = parseGitDiffFiles(stripSubmoduleHeaders(diffContent), parsePatch);
135
135
  const filesWithRawDiff = processFiles(files, formatPatch);
136
136
  const fileNames = filesWithRawDiff.map((file) => getFileName(file));
137
+ const treeFiles = filesWithRawDiff.map((file, idx) => {
138
+ const { additions, deletions } = countChanges(file.hunks);
139
+ return {
140
+ path: getFileName(file),
141
+ status: getFileStatus(file),
142
+ additions,
143
+ deletions,
144
+ fileIndex: idx,
145
+ };
146
+ });
147
+ const treeFileOrder = buildDirectoryTree(treeFiles)
148
+ .filter((node) => node.isFile && node.fileIndex !== undefined)
149
+ .map((node) => node.fileIndex);
137
150
  if (filesWithRawDiff.length === 0) {
138
151
  throw new Error("No files to display");
139
152
  }
@@ -150,57 +163,35 @@ async function renderDiffToFrameWithSectionPositions(diffContent, options) {
150
163
  // Create the diff view component
151
164
  // NOTE: No height: "100%" - let content determine its natural height
152
165
  function WebApp() {
153
- return React.createElement("box", {
154
- style: {
166
+ return (_jsxs("box", { style: {
155
167
  flexDirection: "column",
156
168
  backgroundColor: webBg,
157
- },
158
- }, showNotice
159
- ? renderNoticeBlock({
160
- mutedColor: webMuted,
161
- showExpiry: showExpiryNotice,
162
- })
163
- : null, filesWithRawDiff.map((file, idx) => {
164
- const fileName = getFileName(file);
165
- const oldFileName = getOldFileName(file);
166
- const filetype = detectFiletype(fileName);
167
- const { additions, deletions } = countChanges(file.hunks);
168
- // Use forced viewMode if set, otherwise auto-detect (higher threshold 150 for web vs TUI 100)
169
- const viewMode = options.viewMode || getViewMode(additions, deletions, options.cols, 150);
170
- // Build file header elements - show "old → new" for renames
171
- const fileHeaderChildren = oldFileName
172
- ? [
173
- React.createElement("text", { fg: webMuted, key: "old" }, oldFileName.trim()),
174
- React.createElement("text", { fg: webMuted, key: "arrow" }, " "),
175
- React.createElement("text", { fg: webText, key: "new" }, fileName.trim()),
176
- ]
177
- : [React.createElement("text", { fg: webText, key: "name" }, fileName.trim())];
178
- return React.createElement("box", {
179
- key: idx,
180
- ref: (r) => {
181
- if (r)
182
- fileSectionRefs.set(idx, r);
183
- else
184
- fileSectionRefs.delete(idx);
185
- },
186
- style: { flexDirection: "column", marginBottom: 2 },
187
- }, React.createElement("box", {
188
- style: {
189
- paddingBottom: 1,
190
- paddingLeft: 1,
191
- paddingRight: 1,
192
- flexShrink: 0,
193
- flexDirection: "row",
194
- alignItems: "center",
195
- },
196
- }, ...fileHeaderChildren, React.createElement("text", { fg: "#2d8a47" }, ` +${additions}`), React.createElement("text", { fg: "#c53b53" }, `-${deletions}`)), React.createElement(DiffView, {
197
- diff: file.rawDiff || "",
198
- view: viewMode,
199
- filetype,
200
- themeName,
201
- ...(options.wrapMode && { wrapMode: options.wrapMode }),
202
- }));
203
- }));
169
+ }, children: [showNotice
170
+ ? renderNoticeBlock({
171
+ mutedColor: webMuted,
172
+ showExpiry: showExpiryNotice,
173
+ })
174
+ : null, _jsx("box", { style: { marginBottom: 2 }, children: _jsx(DirectoryTreeView, { files: treeFiles, themeName: themeName }) }), filesWithRawDiff.map((file, idx) => {
175
+ const fileName = getFileName(file);
176
+ const oldFileName = getOldFileName(file);
177
+ const filetype = detectFiletype(fileName);
178
+ const { additions, deletions } = countChanges(file.hunks);
179
+ // Use forced viewMode if set, otherwise auto-detect (higher threshold 150 for web vs TUI 100)
180
+ const viewMode = options.viewMode || getViewMode(additions, deletions, options.cols, 150);
181
+ return (_jsxs("box", { ref: (r) => {
182
+ if (r)
183
+ fileSectionRefs.set(idx, r);
184
+ else
185
+ fileSectionRefs.delete(idx);
186
+ }, style: { flexDirection: "column", marginBottom: 2 }, children: [_jsxs("box", { style: {
187
+ paddingBottom: 1,
188
+ paddingLeft: 1,
189
+ paddingRight: 1,
190
+ flexShrink: 0,
191
+ flexDirection: "row",
192
+ alignItems: "center",
193
+ }, children: [oldFileName ? (_jsxs(_Fragment, { children: [_jsx("text", { fg: webMuted, children: oldFileName.trim() }), _jsx("text", { fg: webMuted, children: " \u2192 " }), _jsx("text", { fg: webText, children: fileName.trim() })] })) : (_jsx("text", { fg: webText, children: fileName.trim() })), _jsxs("text", { fg: "#2d8a47", children: [" +", additions] }), _jsxs("text", { fg: "#c53b53", children: ["-", deletions] })] }), _jsx(DiffView, { diff: file.rawDiff || "", view: viewMode, filetype: filetype, themeName: themeName, wrapMode: options.wrapMode })] }, idx));
194
+ })] }));
204
195
  }
205
196
  // Content-fitting rendering:
206
197
  // 1. Start with small initial height
@@ -213,7 +204,7 @@ async function renderDiffToFrameWithSectionPositions(diffContent, options) {
213
204
  height: currentHeight,
214
205
  });
215
206
  // Mount and do initial render
216
- createRoot(renderer).render(React.createElement(WebApp));
207
+ createRoot(renderer).render(_jsx(WebApp, {}));
217
208
  await renderOnce();
218
209
  // Wait for React to mount components (may take a few render cycles)
219
210
  let contentHeight = getContentHeight(renderer.root);
@@ -246,6 +237,7 @@ async function renderDiffToFrameWithSectionPositions(diffContent, options) {
246
237
  sectionPositions.push({
247
238
  lineIndex: Math.max(0, Math.round(layout.top)),
248
239
  fileName: fileNames[idx],
240
+ fileIndex: idx,
249
241
  });
250
242
  }
251
243
  // Capture the final frame
@@ -261,6 +253,7 @@ async function renderDiffToFrameWithSectionPositions(diffContent, options) {
261
253
  return {
262
254
  frame,
263
255
  sectionPositions,
256
+ treeFileOrder,
264
257
  };
265
258
  }
266
259
  export async function renderDiffToFrame(diffContent, options) {
@@ -324,6 +317,25 @@ export function extractLineNumber(line) {
324
317
  // Prefer new-file (right/second) number; fall back to old-file (left/first)
325
318
  return secondNum ?? firstNum;
326
319
  }
320
+ /**
321
+ * Match a rendered tree file row and extract the file path label.
322
+ * Examples:
323
+ * "│ ├── index.ts (+5,-2)"
324
+ * "└── README.md (-15)"
325
+ */
326
+ export function extractTreeFilePath(lineText) {
327
+ const match = lineText.match(/^\s*[│ ]*[├└]──\s+(.+?)\s+\([^)]*\)\s*$/);
328
+ if (!match || !match[1])
329
+ return null;
330
+ return match[1].trim();
331
+ }
332
+ function escapeHtmlAttribute(value) {
333
+ return value
334
+ .replace(/&/g, "&amp;")
335
+ .replace(/"/g, "&quot;")
336
+ .replace(/</g, "&lt;")
337
+ .replace(/>/g, "&gt;");
338
+ }
327
339
  /**
328
340
  * Build line-indexed anchors from file section layout positions.
329
341
  * This avoids regex detection on rendered text, which can produce
@@ -351,6 +363,8 @@ const SECTION_ANCHOR_CSS = `
351
363
  .file-section { scroll-margin-top: 16px; }
352
364
  .file-link { color: inherit; text-decoration: none; cursor: copy; }
353
365
  .file-link:hover { text-decoration: underline; }
366
+ .tree-file-link { color: inherit; text-decoration: none; }
367
+ .tree-file-link:hover { text-decoration: underline; }
354
368
  `;
355
369
  // JS: scroll to hash fragment on page load + click-to-copy filename on .file-link click.
356
370
  // On click: copies the filename text to clipboard and updates the URL hash.
@@ -379,8 +393,16 @@ export async function captureToHtml(diffContent, options) {
379
393
  const { frameToHtmlDocument } = await import("./ansi-html.js");
380
394
  // Render diff to captured frame (with notice for web uploads)
381
395
  // and collect exact section line positions from layout metadata.
382
- const { frame, sectionPositions } = await renderDiffToFrameWithSectionPositions(diffContent, { ...options, showNotice: true });
396
+ const { frame, sectionPositions, treeFileOrder } = await renderDiffToFrameWithSectionPositions(diffContent, { ...options, showNotice: true });
383
397
  const anchors = buildAnchorMap(sectionPositions);
398
+ const anchorIdByFileIndex = new Map();
399
+ for (const section of sectionPositions) {
400
+ const anchor = anchors.get(section.lineIndex);
401
+ if (anchor) {
402
+ anchorIdByFileIndex.set(section.fileIndex, anchor.id);
403
+ }
404
+ }
405
+ const treeAnchorOrder = treeFileOrder.map((fileIndex) => anchorIdByFileIndex.get(fileIndex) ?? null);
384
406
  // Get theme colors for HTML output
385
407
  const theme = getResolvedTheme(options.themeName);
386
408
  const backgroundColor = rgbaToHex(theme.background);
@@ -398,6 +420,7 @@ export async function captureToHtml(diffContent, options) {
398
420
  const sortedSections = [...sectionPositions].sort((a, b) => a.lineIndex - b.lineIndex);
399
421
  let sectionPtr = 0;
400
422
  let currentFile = null;
423
+ let treeLinkPtr = 0;
401
424
  const renderLineCallback = (defaultHtml, line, lineIndex) => {
402
425
  // Advance current file when we pass a section boundary
403
426
  while (sectionPtr < sortedSections.length && lineIndex >= sortedSections[sectionPtr].lineIndex) {
@@ -405,15 +428,23 @@ export async function captureToHtml(diffContent, options) {
405
428
  sectionPtr++;
406
429
  }
407
430
  let html = defaultHtml;
431
+ if (currentFile === null && treeLinkPtr < treeAnchorOrder.length) {
432
+ const lineText = line.spans.map((span) => span.text).join("");
433
+ const treePath = extractTreeFilePath(lineText);
434
+ if (treePath) {
435
+ const targetAnchor = treeAnchorOrder[treeLinkPtr];
436
+ treeLinkPtr++;
437
+ if (targetAnchor) {
438
+ const escapedPath = escapeHtmlAttribute(treePath);
439
+ html = html.replace(`>${escapedPath}</span>`, `><a href="#${targetAnchor}" class="tree-file-link">${escapedPath}</a></span>`);
440
+ }
441
+ }
442
+ }
408
443
  // File-section header: add id + clickable link
409
444
  const anchor = anchors.get(lineIndex);
410
445
  if (anchor) {
411
446
  html = html.replace('<div class="line">', `<div id="${anchor.id}" class="line file-section">`);
412
- const escapedLabel = anchor.label
413
- .replace(/&/g, "&amp;")
414
- .replace(/</g, "&lt;")
415
- .replace(/>/g, "&gt;")
416
- .replace(/"/g, "&quot;");
447
+ const escapedLabel = escapeHtmlAttribute(anchor.label);
417
448
  html = html.replace(`>${escapedLabel}</span>`, `><a href="#${anchor.id}" class="file-link">${escapedLabel}</a></span>`);
418
449
  }
419
450
  // Diff line: extract line number from spans and add data-anchor.
@@ -423,11 +454,7 @@ export async function captureToHtml(diffContent, options) {
423
454
  const lineNum = extractLineNumber(line);
424
455
  if (lineNum) {
425
456
  const anchorValue = `${currentFile}:${lineNum}`;
426
- const safeAnchor = anchorValue
427
- .replace(/&/g, "&amp;")
428
- .replace(/"/g, "&quot;")
429
- .replace(/</g, "&lt;")
430
- .replace(/>/g, "&gt;");
457
+ const safeAnchor = escapeHtmlAttribute(anchorValue);
431
458
  html = html.replace('<div class="line">', `<div class="line" data-anchor="${safeAnchor}">`);
432
459
  }
433
460
  }
@@ -498,7 +525,6 @@ export async function renderReviewToFrame(options) {
498
525
  const { createTestRenderer } = await import("@opentuah/core/testing");
499
526
  const { createRoot } = await import("@opentuah/react");
500
527
  const { getTreeSitterClient } = await import("@opentuah/core");
501
- const React = await import("react");
502
528
  // Pre-initialize TreeSitter client to ensure syntax highlighting works
503
529
  const tsClient = getTreeSitterClient();
504
530
  await tsClient.initialize();
@@ -523,28 +549,18 @@ export async function renderReviewToFrame(options) {
523
549
  // Pass renderer to enable custom renderNode (wrapMode: "none" for diagrams)
524
550
  // NOTE: No height: "100%" - let content determine its natural height
525
551
  function ReviewWebApp() {
526
- return React.createElement("box", {
527
- style: {
552
+ return (_jsxs("box", { style: {
528
553
  flexDirection: "column",
529
554
  backgroundColor: webBg,
530
- },
531
- }, showNotice
532
- ? renderNoticeBlock({
533
- mutedColor: webMuted,
534
- showExpiry: showExpiryNotice,
535
- })
536
- : null, React.createElement(ReviewAppView, {
537
- hunks: options.hunks,
538
- reviewData: options.reviewData,
539
- isGenerating: false,
540
- themeName,
541
- width: options.cols,
542
- showFooter: false,
543
- renderer: renderer,
544
- }));
555
+ }, children: [showNotice
556
+ ? renderNoticeBlock({
557
+ mutedColor: webMuted,
558
+ showExpiry: showExpiryNotice,
559
+ })
560
+ : null, _jsx(ReviewAppView, { hunks: options.hunks, reviewData: options.reviewData, isGenerating: false, themeName: themeName, width: options.cols, showFooter: false, renderer: renderer })] }));
545
561
  }
546
562
  // Mount and do initial render
547
- createRoot(renderer).render(React.createElement(ReviewWebApp));
563
+ createRoot(renderer).render(_jsx(ReviewWebApp, {}));
548
564
  await renderOnce();
549
565
  // Wait for React to mount components (may take a few render cycles)
550
566
  let contentHeight = getContentHeight(renderer.root);
@@ -657,7 +673,7 @@ export async function captureReviewResponsiveHtml(options) {
657
673
  /**
658
674
  * Upload HTML to the critique.work worker
659
675
  */
660
- export async function uploadHtml(htmlDesktop, htmlMobile, ogImage) {
676
+ export async function uploadHtml(htmlDesktop, htmlMobile, ogImage, patch) {
661
677
  const body = {
662
678
  html: htmlDesktop,
663
679
  htmlMobile,
@@ -666,6 +682,10 @@ export async function uploadHtml(htmlDesktop, htmlMobile, ogImage) {
666
682
  if (ogImage) {
667
683
  body.ogImage = ogImage.toString("base64");
668
684
  }
685
+ // Include raw unified diff (patch) for programmatic access
686
+ if (patch) {
687
+ body.patch = patch;
688
+ }
669
689
  const licenseKey = loadStoredLicenseKey();
670
690
  const ownerSecret = loadOrCreateOwnerSecret();
671
691
  const headers = {
@@ -1 +1 @@
1
- {"version":3,"file":"web-utils.test.d.ts","sourceRoot":"","sources":["../src/web-utils.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"web-utils.test.d.ts","sourceRoot":"","sources":["../src/web-utils.test.tsx"],"names":[],"mappings":""}
@@ -1,9 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentuah/react/jsx-runtime";
1
2
  import { describe, test, expect, afterEach } from "bun:test";
2
3
  import { createTestRenderer } from "@opentuah/core/testing";
3
4
  import { createRoot } from "@opentuah/react";
4
- import React from "react";
5
5
  import { RGBA } from "@opentuah/core";
6
- import { slugifyFileName, buildAnchorMap, extractLineNumber } from "./web-utils.js";
6
+ import { slugifyFileName, buildAnchorMap, extractLineNumber, extractTreeFilePath } from "./web-utils.js";
7
7
  import { frameToHtml, frameToHtmlDocument } from "./ansi-html.js";
8
8
  describe("getSpanLines rendering", () => {
9
9
  let renderer = null;
@@ -16,9 +16,9 @@ describe("getSpanLines rendering", () => {
16
16
  renderer = setup.renderer;
17
17
  const { renderOnce } = setup;
18
18
  function App() {
19
- return React.createElement("box", { style: { flexDirection: "column" } }, React.createElement("text", { content: "├── src/errors" }), React.createElement("text", { content: " └── index.ts" }), React.createElement("text", { content: "└── api" }));
19
+ return (_jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("text", { content: "\u251C\u2500\u2500 src/errors" }), _jsx("text", { content: "\u2502 \u2514\u2500\u2500 index.ts" }), _jsx("text", { content: "\u2514\u2500\u2500 api" })] }));
20
20
  }
21
- createRoot(renderer).render(React.createElement(App));
21
+ createRoot(renderer).render(_jsx(App, {}));
22
22
  for (let i = 0; i < 5; i++) {
23
23
  await renderOnce();
24
24
  await new Promise(r => setTimeout(r, 50));
@@ -36,9 +36,9 @@ describe("getSpanLines rendering", () => {
36
36
  renderer = setup.renderer;
37
37
  const { renderOnce } = setup;
38
38
  function App() {
39
- return React.createElement("text", { content: "src/errors/index.ts" });
39
+ return _jsx("text", { content: "src/errors/index.ts" });
40
40
  }
41
- createRoot(renderer).render(React.createElement(App));
41
+ createRoot(renderer).render(_jsx(App, {}));
42
42
  for (let i = 0; i < 5; i++) {
43
43
  await renderOnce();
44
44
  await new Promise(r => setTimeout(r, 50));
@@ -227,6 +227,17 @@ describe("extractLineNumber", () => {
227
227
  expect(extractLineNumber(line)).toBe("42");
228
228
  });
229
229
  });
230
+ describe("extractTreeFilePath", () => {
231
+ test("matches tree file rows with change stats", () => {
232
+ expect(extractTreeFilePath("│ ├── index.ts (+5,-2)")).toBe("index.ts");
233
+ expect(extractTreeFilePath("└── README.md (-15)")).toBe("README.md");
234
+ expect(extractTreeFilePath("│ └── utils.ts (+30)")).toBe("utils.ts");
235
+ });
236
+ test("does not match directory rows", () => {
237
+ expect(extractTreeFilePath("└── src")).toBe(null);
238
+ expect(extractTreeFilePath("│ ├── components")).toBe(null);
239
+ });
240
+ });
230
241
  describe("data-anchor in rendered HTML", () => {
231
242
  test("injects data-anchor with file and line number", () => {
232
243
  // Simulate a frame with a file header line and diff lines
@@ -300,6 +311,44 @@ describe("data-anchor in rendered HTML", () => {
300
311
  expect(html).toContain('data-anchor="src/&quot;quoted&quot;.ts:5"');
301
312
  });
302
313
  });
314
+ describe("tree links in rendered HTML", () => {
315
+ test("adds href links to tree file rows without ids", () => {
316
+ const frame = {
317
+ cols: 80,
318
+ rows: 3,
319
+ cursor: [0, 0],
320
+ lines: [
321
+ { spans: [mockSpan("└── src")] },
322
+ { spans: [mockSpan(" └── "), mockSpan("foo.ts"), mockSpan(" (+2,-1)")] },
323
+ { spans: [mockSpan("src/foo.ts")] },
324
+ ],
325
+ };
326
+ let treeLinkPtr = 0;
327
+ const treeAnchorOrder = ["src-foo-ts"];
328
+ const { html } = frameToHtml(frame, {
329
+ renderLine: (defaultHtml, line, lineIndex) => {
330
+ let out = defaultHtml;
331
+ if (lineIndex < 2 && treeLinkPtr < treeAnchorOrder.length) {
332
+ const treePath = extractTreeFilePath(line.spans.map((span) => span.text).join(""));
333
+ if (treePath) {
334
+ const targetAnchor = treeAnchorOrder[treeLinkPtr];
335
+ treeLinkPtr++;
336
+ out = out.replace(`>${treePath}</span>`, `><a href="#${targetAnchor}" class="tree-file-link">${treePath}</a></span>`);
337
+ }
338
+ }
339
+ if (lineIndex === 2) {
340
+ out = out.replace('<div class="line">', '<div id="src-foo-ts" class="line file-section">');
341
+ }
342
+ return out;
343
+ },
344
+ });
345
+ const htmlLines = html.split("\n");
346
+ expect(htmlLines[1]).toContain('href="#src-foo-ts"');
347
+ expect(htmlLines[1]).toContain('class="tree-file-link"');
348
+ expect(htmlLines[1]).not.toContain('id="src-foo-ts"');
349
+ expect(htmlLines[2]).toContain('id="src-foo-ts"');
350
+ });
351
+ });
303
352
  describe("ansi-html extraCss and extraJs", () => {
304
353
  test("extraCss is injected into document style block", () => {
305
354
  const frame = mockFrame(["test"]);
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.tsx"],"names":[],"mappings":"AAAA,gCAAgC;AAMhC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAQ5D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEnD,KAAK,QAAQ,GAAG;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE;QAAE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAC;QAAC,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG;YAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;SAAE,CAAA;KAAE,CAAA;CAChI,CAAA;AAUD,QAAA,MAAM,GAAG;cAAwB,QAAQ;eAAa;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE;yCAAK,CAAA;AAo2B7E,eAAe,GAAG,CAAA"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.tsx"],"names":[],"mappings":"AAAA,gCAAgC;AAMhC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAiB5D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEnD,KAAK,QAAQ,GAAG;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE;QAAE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAC;QAAC,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG;YAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;SAAE,CAAA;KAAE,CAAA;CAChI,CAAA;AAUD,QAAA,MAAM,GAAG;cAAwB,QAAQ;eAAa;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE;yCAAK,CAAA;AAi8B7E,eAAe,GAAG,CAAA"}