critique 0.1.119 → 0.1.121

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.
Files changed (42) hide show
  1. package/dist/agentation-widget.js +39 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +29 -3
  4. package/dist/diff-utils.d.ts.map +1 -1
  5. package/dist/diff-utils.js +23 -0
  6. package/dist/diff-utils.test.js +35 -0
  7. package/dist/hooks/use-copy-selection.d.ts.map +1 -1
  8. package/dist/hooks/use-copy-selection.js +8 -7
  9. package/dist/image.d.ts +1 -0
  10. package/dist/image.d.ts.map +1 -1
  11. package/dist/image.js +1 -0
  12. package/dist/routes/annotations-context.d.ts +0 -1
  13. package/dist/routes/annotations-context.d.ts.map +1 -1
  14. package/dist/routes/annotations-context.js +46 -41
  15. package/dist/web-utils.d.ts +29 -1
  16. package/dist/web-utils.d.ts.map +1 -1
  17. package/dist/web-utils.js +199 -59
  18. package/dist/web-utils.test.js +119 -1
  19. package/dist/worker.d.ts.map +1 -1
  20. package/dist/worker.js +56 -13
  21. package/package.json +3 -3
  22. package/public/agentation-widget.js +49 -3
  23. package/src/.test-stdin-pager-tmp/binary.diff +3 -0
  24. package/src/.test-stdin-pager-tmp/colored.diff +8 -0
  25. package/src/.test-stdin-pager-tmp/context-only.diff +7 -0
  26. package/src/.test-stdin-pager-tmp/delete.diff +9 -0
  27. package/src/.test-stdin-pager-tmp/empty.diff +0 -0
  28. package/src/.test-stdin-pager-tmp/large.diff +19 -0
  29. package/src/.test-stdin-pager-tmp/multi.diff +20 -0
  30. package/src/.test-stdin-pager-tmp/newfile.diff +12 -0
  31. package/src/.test-stdin-pager-tmp/rename.diff +11 -0
  32. package/src/.test-stdin-pager-tmp/single.diff +8 -0
  33. package/src/agentation-widget.tsx +42 -0
  34. package/src/cli.tsx +29 -2
  35. package/src/diff-utils.test.ts +42 -0
  36. package/src/diff-utils.ts +24 -0
  37. package/src/hooks/use-copy-selection.ts +7 -7
  38. package/src/image.ts +2 -1
  39. package/src/routes/annotations-context.tsx +71 -121
  40. package/src/web-utils.test.ts +141 -1
  41. package/src/web-utils.tsx +230 -70
  42. package/src/worker.tsx +61 -17
@@ -13,6 +13,13 @@
13
13
  // containing many spans). We inject content-visibility: auto on .line elements
14
14
  // so the browser only renders visible lines, skipping layout/paint for the
15
15
  // ~6,400 off-screen lines. This brings page load from ~16s to <2s on large diffs.
16
+ //
17
+ // iOS Safari pinch-zoom fix:
18
+ // position:fixed is relative to the layout viewport, not the visual viewport.
19
+ // When users pinch-to-zoom on iOS Safari, the widget drifts with the zoomed
20
+ // content and can end up mispositioned after zooming out. We use the
21
+ // visualViewport API to apply a counter-transform that keeps the widget
22
+ // anchored to the bottom-right of what the user actually sees.
16
23
  function injectContentVisibility() {
17
24
  if (document.getElementById("critique-cv-styles"))
18
25
  return;
@@ -37,6 +44,9 @@ async function init() {
37
44
  const isLight = window.matchMedia("(prefers-color-scheme: light)").matches;
38
45
  localStorage.setItem("feedback-toolbar-theme", isLight ? "light" : "dark");
39
46
  }
47
+ // Always reset the widget to the default bottom-right position on page load.
48
+ // If a user accidentally drags it, it snaps back on refresh.
49
+ localStorage.removeItem("feedback-toolbar-position");
40
50
  if (document.getElementById("critique-agentation"))
41
51
  return;
42
52
  const config = window.__CRITIQUE_CONFIG__;
@@ -47,6 +57,11 @@ async function init() {
47
57
  const container = document.createElement("div");
48
58
  container.id = "critique-agentation";
49
59
  document.body.appendChild(container);
60
+ // Fix position:fixed drift during pinch-to-zoom on iOS Safari.
61
+ // The visualViewport API tells us how the visible area has shifted
62
+ // relative to the layout viewport. We apply a counter-transform so
63
+ // the widget stays anchored to the visual viewport's bottom-right.
64
+ setupVisualViewportFix(container);
50
65
  // Use preact's render() directly since the build aliases react → preact/compat.
51
66
  // preact/compat does not export createRoot from react-dom/client.
52
67
  const [{ Agentation }, { render, h }] = await Promise.all([
@@ -59,6 +74,30 @@ async function init() {
59
74
  showFreezeButton: config.showFreezeButton,
60
75
  }), container);
61
76
  }
77
+ function setupVisualViewportFix(container) {
78
+ const vv = window.visualViewport;
79
+ if (!vv)
80
+ return;
81
+ function update() {
82
+ if (!vv)
83
+ return;
84
+ if (vv.scale <= 1.01) {
85
+ // No zoom — clear any transform so default fixed positioning works
86
+ container.style.transform = "";
87
+ return;
88
+ }
89
+ // Translate the container so it follows the visual viewport's position,
90
+ // then scale it back to its original size (counter the zoom).
91
+ container.style.transform = [
92
+ `translate(${vv.offsetLeft}px, ${vv.offsetTop}px)`,
93
+ `scale(${1 / vv.scale})`,
94
+ ].join(" ");
95
+ // Set transform-origin to top-left so the translate values are intuitive
96
+ container.style.transformOrigin = "top left";
97
+ }
98
+ vv.addEventListener("resize", update);
99
+ vv.addEventListener("scroll", update);
100
+ }
62
101
  if (document.readyState === "loading") {
63
102
  document.addEventListener("DOMContentLoaded", init);
64
103
  }
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";AAOA,OAAO,gCAAgC,CAAC;AAYxC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwB/B,OAAO,EAgBL,KAAK,UAAU,EAEhB,MAAM,iBAAiB,CAAC;AAi4CzB,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,YAAY,CAkVjE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";AAOA,OAAO,gCAAgC,CAAC;AAYxC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwB/B,OAAO,EAgBL,KAAK,UAAU,EAEhB,MAAM,iBAAiB,CAAC;AA45CzB,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC,YAAY,CAkVjE"}
package/dist/cli.js CHANGED
@@ -886,7 +886,7 @@ async function runResumeMode(options) {
886
886
  , isGenerating: false, initialReviewData: review.reviewYaml }) }));
887
887
  }
888
888
  async function runWebMode(diffContent, options) {
889
- const { captureResponsiveHtml, uploadHtml, openInBrowser, } = await import("./web-utils.js");
889
+ const { captureResponsiveHtml, uploadHtml, uploadOgImage, openInBrowser, } = await import("./web-utils.js");
890
890
  // Use stderr for progress when --json is set, stdout otherwise
891
891
  const log = options.json ? console.error.bind(console) : console.log.bind(console);
892
892
  const desktopCols = options.cols || 230;
@@ -904,9 +904,12 @@ async function runWebMode(diffContent, options) {
904
904
  }, 100); // base padding
905
905
  log("Converting to HTML...");
906
906
  try {
907
- const { htmlDesktop, htmlMobile, ogImage } = await captureResponsiveHtml(diffContent, { desktopCols, mobileCols, baseRows, themeName, title: options.title });
907
+ // Render desktop + mobile HTML and OG image in parallel.
908
+ // Skip OG from initial upload — we'll PATCH it in the background
909
+ // after the URL is printed, so the user sees the URL faster.
910
+ const { htmlDesktop, htmlMobile, ogImage } = await captureResponsiveHtml(diffContent, { desktopCols, mobileCols, baseRows, themeName, title: options.title, skipOgImage: true });
908
911
  log("Uploading...");
909
- const result = await uploadHtml(htmlDesktop, htmlMobile, ogImage);
912
+ const result = await uploadHtml(htmlDesktop, htmlMobile);
910
913
  log(`\nPreview URL: ${result.url}`);
911
914
  log(formatPreviewExpiry(result.expiresInDays));
912
915
  if (typeof result.expiresInDays === "number") {
@@ -926,6 +929,29 @@ async function runWebMode(diffContent, options) {
926
929
  if (options.open) {
927
930
  await openInBrowser(result.url);
928
931
  }
932
+ // Generate and upload OG image in the background after URL is printed.
933
+ // The URL is already available — this just adds social media previews.
934
+ // Hard cap at 8s to prevent the process hanging after URL output.
935
+ const ogUpload = (async () => {
936
+ try {
937
+ const { renderDiffToOgImage } = await import("./image.js");
938
+ const ogImg = await renderDiffToOgImage(diffContent, {
939
+ themeName: "github-light",
940
+ stabilizeMs: 100,
941
+ });
942
+ if (ogImg) {
943
+ await uploadOgImage(result.id, ogImg);
944
+ }
945
+ }
946
+ catch {
947
+ // OG image generation failed — not critical, skip silently
948
+ }
949
+ })();
950
+ // Wait for OG upload with a hard timeout so the process always exits
951
+ await Promise.race([
952
+ ogUpload,
953
+ new Promise(resolve => setTimeout(resolve, 8000)),
954
+ ]);
929
955
  process.exit(0);
930
956
  }
931
957
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"diff-utils.d.ts","sourceRoot":"","sources":["../src/diff-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff-utils.d.ts","sourceRoot":"","sources":["../src/diff-utils.ts"],"names":[],"mappings":"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"}
@@ -236,6 +236,13 @@ export function buildGitCommand(options) {
236
236
  const filterArg = filters.length > 0
237
237
  ? `-- ${filters.map((f) => `'${f}'`).join(" ")}`
238
238
  : "";
239
+ // If --commit contains range syntax (A..B or A...B), treat it as a base ref
240
+ // instead. git show with ranges outputs commit metadata interleaved with diffs
241
+ // that parsePatch cannot parse. Redirecting to base reuses the existing range
242
+ // handling below (two-dot and three-dot parsing).
243
+ if (options.commit?.includes("..")) {
244
+ options = { ...options, base: options.commit, commit: undefined };
245
+ }
239
246
  if (options.staged) {
240
247
  return `git diff --cached --no-prefix ${renameArg} ${submoduleArg} ${contextArg} ${filterArg}`.trim();
241
248
  }
@@ -436,9 +443,15 @@ export function detectFiletype(filePath) {
436
443
  case "cts":
437
444
  return "typescript";
438
445
  case "json":
446
+ case "jsonc":
447
+ case "json5":
439
448
  return "json";
440
449
  case "md":
441
450
  case "mdx":
451
+ case "mkd":
452
+ case "mkdn":
453
+ case "mdown":
454
+ case "markdown":
442
455
  return "markdown";
443
456
  case "zig":
444
457
  return "zig";
@@ -456,6 +469,10 @@ export function detectFiletype(filePath) {
456
469
  case "cxx":
457
470
  case "hpp":
458
471
  case "hxx":
472
+ case "hh":
473
+ case "tpp":
474
+ case "ipp":
475
+ case "inl":
459
476
  case "h":
460
477
  return "cpp";
461
478
  case "cs":
@@ -463,6 +480,7 @@ export function detectFiletype(filePath) {
463
480
  case "sh":
464
481
  case "bash":
465
482
  case "zsh":
483
+ case "ksh":
466
484
  return "bash";
467
485
  case "c":
468
486
  return "c";
@@ -479,6 +497,9 @@ export function detectFiletype(filePath) {
479
497
  return "scala";
480
498
  case "html":
481
499
  case "htm":
500
+ case "xhtml":
501
+ case "xml":
502
+ case "svg":
482
503
  return "html";
483
504
  case "yaml":
484
505
  case "yml":
@@ -487,6 +508,8 @@ export function detectFiletype(filePath) {
487
508
  case "lhs":
488
509
  return "haskell";
489
510
  case "css":
511
+ case "scss":
512
+ case "less":
490
513
  return "css";
491
514
  case "jl":
492
515
  return "julia";
@@ -459,6 +459,41 @@ describe("getOldFileName", () => {
459
459
  });
460
460
  });
461
461
  // ============================================================================
462
+ // --commit with range syntax (HEAD~2..HEAD)
463
+ // ============================================================================
464
+ describe("--commit with range syntax", () => {
465
+ it("should use git diff instead of git show for two-dot range", () => {
466
+ // --commit with range redirects to base, which uses the two-dot path
467
+ const cmd = buildGitCommand({ commit: "HEAD~2..HEAD" });
468
+ expect(cmd).toStartWith("git diff HEAD~2..HEAD");
469
+ expect(cmd).not.toContain("git show");
470
+ });
471
+ it("should use git diff instead of git show for three-dot range", () => {
472
+ // --commit with range redirects to base, which uses the three-dot path
473
+ const cmd = buildGitCommand({ commit: "main...feature" });
474
+ expect(cmd).toStartWith("git diff main...feature");
475
+ expect(cmd).not.toContain("git show");
476
+ });
477
+ it("should use git diff for named ref range", () => {
478
+ const cmd = buildGitCommand({ commit: "origin/main..HEAD" });
479
+ expect(cmd).toStartWith("git diff origin/main..HEAD");
480
+ expect(cmd).not.toContain("git show");
481
+ });
482
+ it("should produce same command whether range comes via --commit or positional base", () => {
483
+ const viaCommit = buildGitCommand({ commit: "HEAD~2..HEAD" });
484
+ const viaBase = buildGitCommand({ base: "HEAD~2..HEAD" });
485
+ expect(viaCommit).toBe(viaBase);
486
+ });
487
+ it("should still use git show for a single commit ref", () => {
488
+ const cmd = buildGitCommand({ commit: "HEAD" });
489
+ expect(cmd).toStartWith("git show HEAD");
490
+ });
491
+ it("should still use git show for a single hash", () => {
492
+ const cmd = buildGitCommand({ commit: "abc123" });
493
+ expect(cmd).toStartWith("git show abc123");
494
+ });
495
+ });
496
+ // ============================================================================
462
497
  // buildGitCommand includes rename detection
463
498
  // ============================================================================
464
499
  describe("buildGitCommand with rename detection", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-copy-selection.d.ts","sourceRoot":"","sources":["../../src/hooks/use-copy-selection.ts"],"names":[],"mappings":"AAkFA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CAqBxD"}
1
+ {"version":3,"file":"use-copy-selection.d.ts","sourceRoot":"","sources":["../../src/hooks/use-copy-selection.ts"],"names":[],"mappings":"AAiFA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CAsBxD"}
@@ -2,12 +2,12 @@
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
  import { useRenderer } from "@opentuah/react";
5
- import { spawn } from "child_process";
5
+ import childProcess from "child_process";
6
6
  /**
7
7
  * Copy text to system clipboard using native commands.
8
8
  * Falls back to OSC52 escape sequence for terminal clipboard (works over SSH).
9
9
  */
10
- async function copyToClipboard(text) {
10
+ async function copyToClipboard(text, copyOsc52) {
11
11
  const platform = process.platform;
12
12
  // Try native clipboard commands
13
13
  try {
@@ -52,16 +52,15 @@ async function copyToClipboard(text) {
52
52
  catch {
53
53
  // Native clipboard failed, fall through to OSC52
54
54
  }
55
- // Fallback: OSC52 escape sequence (works in many terminals, including over SSH)
56
- const encoded = Buffer.from(text).toString("base64");
57
- process.stdout.write(`\x1b]52;c;${encoded}\x07`);
55
+ // Fallback: renderer OSC52 utility (works in many terminals, including over SSH)
56
+ copyOsc52(text);
58
57
  }
59
58
  /**
60
59
  * Spawn a clipboard command and pipe text to it
61
60
  */
62
61
  function spawnClipboard(cmd, args, text) {
63
62
  return new Promise((resolve, reject) => {
64
- const proc = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
63
+ const proc = childProcess.spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
65
64
  proc.on("error", reject);
66
65
  proc.on("close", (code) => {
67
66
  if (code === 0)
@@ -99,11 +98,13 @@ export function useCopySelection() {
99
98
  const selection = renderer.getSelection();
100
99
  if (!selection)
101
100
  return;
101
+ if (selection.isDragging)
102
+ return;
102
103
  const text = selection.getSelectedText();
103
104
  if (!text || text.length === 0)
104
105
  return;
105
106
  try {
106
- await copyToClipboard(text);
107
+ await copyToClipboard(text, (value) => renderer.copyToClipboardOSC52(value));
107
108
  }
108
109
  catch {
109
110
  // Silent fail - user can manually copy if needed
package/dist/image.d.ts CHANGED
@@ -129,5 +129,6 @@ export declare function renderFrameToOgImage(frame: CapturedFrame, options?: OgI
129
129
  */
130
130
  export declare function renderDiffToOgImage(diffContent: string, options?: OgImageOptions & {
131
131
  cols?: number;
132
+ stabilizeMs?: number;
132
133
  }): Promise<Buffer>;
133
134
  //# sourceMappingURL=image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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"}
package/dist/image.js CHANGED
@@ -177,6 +177,7 @@ export async function renderDiffToOgImage(diffContent, options = {}) {
177
177
  cols,
178
178
  maxRows: 200,
179
179
  themeName,
180
+ stabilizeMs: options.stabilizeMs,
180
181
  });
181
182
  // Convert frame to OG image
182
183
  return renderFrameToOgImage(frame, {
@@ -1,4 +1,3 @@
1
- /** @jsxImportSource hono/jsx */
2
1
  import { Hono } from "hono";
3
2
  import type { KVNamespace } from "@cloudflare/workers-types";
4
3
  type Bindings = {
@@ -1 +1 @@
1
- {"version":3,"file":"annotations-context.d.ts","sourceRoot":"","sources":["../../src/routes/annotations-context.tsx"],"names":[],"mappings":"AAAA,gCAAgC;AAKhC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D,KAAK,QAAQ,GAAG;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE;QACX,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAA;QAChD,GAAG,CAAC,EAAE,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,GAAG;YAC/B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;SAC3C,CAAA;KACF,CAAA;CACF,CAAA;AAED,QAAA,MAAM,KAAK;cAAwB,QAAQ;yCAAK,CAAA;AA0IhD,OAAO,EAAE,KAAK,IAAI,uBAAuB,EAAE,CAAA"}
1
+ {"version":3,"file":"annotations-context.d.ts","sourceRoot":"","sources":["../../src/routes/annotations-context.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D,KAAK,QAAQ,GAAG;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE;QACX,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAA;QAChD,GAAG,CAAC,EAAE,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,GAAG;YAC/B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;SAC3C,CAAA;KACF,CAAA;CACF,CAAA;AAgED,QAAA,MAAM,KAAK;cAAwB,QAAQ;yCAAK,CAAA;AA2BhD,OAAO,EAAE,KAAK,IAAI,uBAAuB,EAAE,CAAA"}
@@ -1,9 +1,46 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
- /** @jsxImportSource hono/jsx */
3
1
  // Route: GET /v/:id/annotations
4
- // Returns all annotations for a diff with surrounding context.
5
- // Supports ?format=json for machine-readable output, otherwise renders HTML.
2
+ // Returns all annotations as markdown, matching agentation's generateOutput format.
3
+ // This is the format agents and LLMs consume best.
6
4
  import { Hono } from "hono";
5
+ function generateMarkdown(annotations, diffUrl) {
6
+ if (annotations.length === 0)
7
+ return "No annotations yet.\n";
8
+ let output = `## Page Feedback: ${diffUrl}\n\n`;
9
+ for (let i = 0; i < annotations.length; i++) {
10
+ const a = annotations[i];
11
+ output += `### ${i + 1}. ${a.element || a.elementPath}\n`;
12
+ if (a.elementPath) {
13
+ output += `**Location:** ${a.elementPath}\n`;
14
+ }
15
+ if (a.anchor) {
16
+ output += `**Anchor:** ${a.anchor}\n`;
17
+ }
18
+ if (a.selectedText) {
19
+ output += `**Selected text:** "${a.selectedText}"\n`;
20
+ }
21
+ if (a.nearbyText && !a.selectedText) {
22
+ output += `**Context:** ${a.nearbyText.slice(0, 100)}\n`;
23
+ }
24
+ if (a.status && a.status !== "pending") {
25
+ output += `**Status:** ${a.status}\n`;
26
+ }
27
+ if (a.severity) {
28
+ output += `**Severity:** ${a.severity}\n`;
29
+ }
30
+ if (a.intent) {
31
+ output += `**Intent:** ${a.intent}\n`;
32
+ }
33
+ output += `**Feedback:** ${a.comment}\n`;
34
+ if (a.thread && a.thread.length > 0) {
35
+ output += "\n";
36
+ for (const msg of a.thread) {
37
+ output += `> **${msg.role}:** ${msg.content}\n`;
38
+ }
39
+ }
40
+ output += "\n";
41
+ }
42
+ return output.trim() + "\n";
43
+ }
7
44
  const route = new Hono();
8
45
  route.get("/v/:id/annotations", async (c) => {
9
46
  const id = c.req.param("id");
@@ -18,43 +55,11 @@ route.get("/v/:id/annotations", async (c) => {
18
55
  req.headers.set("x-partykit-room", id);
19
56
  const resp = await stub.fetch(req);
20
57
  const annotations = (await resp.json());
21
- const format = c.req.query("format");
22
- // JSON format for agents/bots
23
- if (format === "json") {
24
- return c.json({
25
- diffId: id,
26
- diffUrl: `${new URL(c.req.url).origin}/v/${id}`,
27
- count: annotations.length,
28
- annotations,
29
- });
30
- }
31
- // HTML format for humans
32
58
  const origin = new URL(c.req.url).origin;
33
- return c.html(_jsxs("html", { children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsxs("title", { children: ["Annotations \u2014 ", id.slice(0, 8)] }), _jsx("style", { children: `
34
- body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; background: #0f1419; color: #e6edf3; margin: 0; padding: 24px; }
35
- .header { max-width: 720px; margin: 0 auto 24px; }
36
- .header h1 { font-size: 20px; margin: 0 0 8px; }
37
- .header a { color: #58a6ff; text-decoration: none; font-size: 14px; }
38
- .header a:hover { text-decoration: underline; }
39
- .count { color: #8b949e; font-size: 14px; margin-bottom: 24px; }
40
- .annotation { max-width: 720px; margin: 0 auto 20px; background: #151b23; border: 1px solid #2d3440; border-radius: 10px; padding: 16px; }
41
- .meta { display: flex; gap: 12px; align-items: center; margin-bottom: 8px; font-size: 13px; color: #8b949e; }
42
- .badge { padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; }
43
- .badge-pending { background: #1f3044; color: #58a6ff; }
44
- .badge-resolved { background: #1a3520; color: #3fb950; }
45
- .badge-dismissed { background: #3d1f20; color: #f85149; }
46
- .badge-acknowledged { background: #3b2e1a; color: #d29922; }
47
- .badge-blocking { background: #3d1f20; color: #f85149; }
48
- .badge-important { background: #3b2e1a; color: #d29922; }
49
- .badge-suggestion { background: #1f3044; color: #58a6ff; }
50
- .comment { font-size: 15px; line-height: 1.6; margin: 8px 0; }
51
- .context { font-size: 13px; color: #8b949e; margin-top: 8px; }
52
- .context code { background: #1c2128; padding: 2px 6px; border-radius: 4px; font-family: ui-monospace, monospace; }
53
- .thread { margin-top: 12px; border-top: 1px solid #2d3440; padding-top: 12px; }
54
- .thread-msg { margin-bottom: 8px; font-size: 13px; }
55
- .thread-role { font-weight: 600; color: #58a6ff; }
56
- .thread-role.agent { color: #d29922; }
57
- .empty { max-width: 720px; margin: 40px auto; text-align: center; color: #8b949e; }
58
- ` })] }), _jsxs("body", { children: [_jsxs("div", { class: "header", children: [_jsx("h1", { children: "Annotations" }), _jsx("a", { href: `${origin}/v/${id}`, children: "Back to diff" }), _jsxs("div", { class: "count", children: [annotations.length, " annotation", annotations.length !== 1 ? "s" : ""] })] }), annotations.length === 0 ? (_jsx("div", { class: "empty", children: "No annotations yet" })) : (annotations.map((ann) => (_jsxs("div", { class: "annotation", children: [_jsxs("div", { class: "meta", children: [ann.userName && _jsx("span", { children: ann.userName }), ann.status && (_jsx("span", { class: `badge badge-${ann.status}`, children: ann.status })), ann.severity && (_jsx("span", { class: `badge badge-${ann.severity}`, children: ann.severity })), ann.intent && _jsx("span", { children: ann.intent }), ann.createdAt && (_jsx("span", { children: new Date(ann.createdAt).toLocaleString() }))] }), _jsx("div", { class: "comment", children: ann.comment }), (ann.elementPath || ann.anchor || ann.nearbyText || ann.selectedText) && (_jsxs("div", { class: "context", children: [ann.anchor && (_jsxs("div", { children: ["Anchor: ", _jsx("code", { children: ann.anchor })] })), ann.elementPath && (_jsxs("div", { children: ["Element: ", _jsx("code", { children: ann.elementPath })] })), ann.selectedText && (_jsxs("div", { children: ["Selected: \"", ann.selectedText, "\""] })), ann.nearbyText && (_jsxs("div", { children: ["Nearby: \"", ann.nearbyText, "\""] }))] })), ann.thread && ann.thread.length > 0 && (_jsx("div", { class: "thread", children: ann.thread.map((msg, i) => (_jsxs("div", { class: "thread-msg", children: [_jsxs("span", { class: `thread-role ${msg.role}`, children: [msg.role, ":"] }), " ", msg.content] }, i))) }))] }, ann.id))))] })] }));
59
+ const diffUrl = `${origin}/v/${id}`;
60
+ const markdown = generateMarkdown(annotations, diffUrl);
61
+ return c.text(markdown, 200, {
62
+ "Content-Type": "text/markdown; charset=utf-8",
63
+ });
59
64
  });
60
65
  export { route as annotationsContextRoute };
@@ -1,4 +1,4 @@
1
- import type { CapturedFrame } from "@opentuah/core";
1
+ import type { CapturedFrame, CapturedLine } from "@opentuah/core";
2
2
  import type { IndexedHunk, ReviewYaml } from "./review/types.js";
3
3
  export declare const WORKER_URL: string;
4
4
  export interface CaptureOptions {
@@ -12,6 +12,9 @@ export interface CaptureOptions {
12
12
  viewMode?: "split" | "unified";
13
13
  /** Show privacy/expiry notice block at top (default: false, enabled for web uploads) */
14
14
  showNotice?: boolean;
15
+ /** How long to wait for async rendering (tree-sitter) to stabilize.
16
+ * Default: 500ms for interactive TUI, use 100ms for batch/web mode. */
17
+ stabilizeMs?: number;
15
18
  }
16
19
  export interface UploadResult {
17
20
  url: string;
@@ -25,6 +28,17 @@ export declare function renderDiffToFrame(diffContent: string, options: CaptureO
25
28
  * e.g. "src/components/foo-bar.tsx" → "src-components-foo-bar-tsx"
26
29
  */
27
30
  export declare function slugifyFileName(name: string): string;
31
+ /**
32
+ * Extract line numbers from a captured diff line's spans.
33
+ * In split view each row has two line number columns (old + new):
34
+ * " " "29" " - " ...content... " " "29" " + " ...content...
35
+ * In unified view there's only one.
36
+ *
37
+ * Returns the new-file (right) line number when available,
38
+ * falling back to the old-file (left) number for deleted-only rows.
39
+ * Returns null for non-diff lines (headers, hunk markers, etc.).
40
+ */
41
+ export declare function extractLineNumber(line: CapturedLine): string | null;
28
42
  /**
29
43
  * Build line-indexed anchors from file section layout positions.
30
44
  * This avoids regex detection on rendered text, which can produce
@@ -50,6 +64,10 @@ export declare function captureResponsiveHtml(diffContent: string, options: {
50
64
  baseRows: number;
51
65
  themeName: string;
52
66
  title?: string;
67
+ /** Stabilization timeout for tree-sitter highlighting (default: 100ms for web) */
68
+ stabilizeMs?: number;
69
+ /** Skip OG image generation for faster URL delivery */
70
+ skipOgImage?: boolean;
53
71
  }): Promise<{
54
72
  htmlDesktop: string;
55
73
  htmlMobile: string;
@@ -80,6 +98,10 @@ export declare function captureReviewResponsiveHtml(options: {
80
98
  baseRows: number;
81
99
  themeName: string;
82
100
  title?: string;
101
+ /** Stabilization timeout for tree-sitter highlighting (default: 100ms for web) */
102
+ stabilizeMs?: number;
103
+ /** Skip OG image generation for faster URL delivery */
104
+ skipOgImage?: boolean;
83
105
  }): Promise<{
84
106
  htmlDesktop: string;
85
107
  htmlMobile: string;
@@ -89,6 +111,12 @@ export declare function captureReviewResponsiveHtml(options: {
89
111
  * Upload HTML to the critique.work worker
90
112
  */
91
113
  export declare function uploadHtml(htmlDesktop: string, htmlMobile: string, ogImage?: Buffer | null): Promise<UploadResult>;
114
+ /**
115
+ * Upload OG image to an existing diff via PATCH.
116
+ * Called in the background after the initial upload returns the URL.
117
+ * Uses a 5s timeout to prevent hanging the process after URL is printed.
118
+ */
119
+ export declare function uploadOgImage(id: string, ogImage: Buffer): Promise<void>;
92
120
  /**
93
121
  * Open a URL in the default browser
94
122
  */
@@ -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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"}