diff --git a/services/hackbot-ui/app/globals.css b/services/hackbot-ui/app/globals.css index cca871a241..c785f9db87 100644 --- a/services/hackbot-ui/app/globals.css +++ b/services/hackbot-ui/app/globals.css @@ -811,3 +811,32 @@ ul.chips li.chip { .test-plan-summary { margin-top: 24px; } +.patch-file { + margin-bottom: 20px; + overflow-x: auto; +} +.patch-file:last-child { + margin-bottom: 0; +} +.patch-file-header { + padding: 8px 12px; + margin-bottom: 8px; + background: var(--panel-2); + border: 1px solid var(--border); + border-radius: 6px; + font-family: var(--mono); + font-size: 13px; + font-weight: 600; + color: var(--text); + word-break: break-all; + overflow-wrap: anywhere; +} +.patch-diff { + --diff-background-color: var(--bg); + --diff-text-color: var(--text); + --diff-selection-background-color: rgba(74, 122, 240, 0.3); + --diff-gutter-insert-background-color: rgba(46, 160, 67, 0.18); + --diff-gutter-delete-background-color: rgba(229, 83, 75, 0.18); + --diff-code-insert-background-color: rgba(46, 160, 67, 0.1); + --diff-code-delete-background-color: rgba(229, 83, 75, 0.1); +} diff --git a/services/hackbot-ui/app/layout.tsx b/services/hackbot-ui/app/layout.tsx index 6a6202b021..b34b7ebd2c 100644 --- a/services/hackbot-ui/app/layout.tsx +++ b/services/hackbot-ui/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; import Link from "next/link"; -import "diff2html/bundles/css/diff2html.min.css"; +import "react-diff-view/style/index.css"; import "./globals.css"; import { SentryUser } from "@/components/SentryUser"; diff --git a/services/hackbot-ui/components/PatchView.tsx b/services/hackbot-ui/components/PatchView.tsx index c62cb85d27..1a34a523ba 100644 --- a/services/hackbot-ui/components/PatchView.tsx +++ b/services/hackbot-ui/components/PatchView.tsx @@ -1,7 +1,6 @@ "use client"; -import { html, parse } from "diff2html"; -import { ColorSchemeType } from "diff2html/lib/types"; +import { parseDiff, Diff, Hunk } from "react-diff-view"; import { useEffect, useState } from "react"; // The artifact every agent that edits source publishes (see @@ -19,7 +18,7 @@ export function PatchPanel({ diff: string; truncated?: boolean; }) { - const files = parse(diff); + const files = parseDiff(diff); return (
@@ -36,18 +35,32 @@ export function PatchPanel({ from the Artifacts list for the rest.

)} - {/* diff2html only emits markup for the diff it was given and escapes - its content, so this does not render arbitrary agent HTML. */} -
1, - matching: "lines", - colorScheme: ColorSchemeType.DARK, - }), - }} - /> +
+ {files.map((file) => ( +
+ {/* Only label files when there's more than one to tell apart. */} + {files.length > 1 && ( +
+ {/* A deleted file's newPath is /dev/null; fall back to + oldPath so the header shows the actual filename. */} + {file.newPath !== "/dev/null" ? file.newPath : file.oldPath} +
+ )} + + {(hunks) => + hunks.map((hunk) => ) + } + +
+ ))} +
)}
diff --git a/services/hackbot-ui/package-lock.json b/services/hackbot-ui/package-lock.json index 360a74794b..10ac2d4f77 100644 --- a/services/hackbot-ui/package-lock.json +++ b/services/hackbot-ui/package-lock.json @@ -10,9 +10,9 @@ "dependencies": { "@sentry/nextjs": "^10.74.0", "better-auth": "^1.6.22", - "diff2html": "^3.4.56", "next": "^15.5.22", "react": "^19.0.0", + "react-diff-view": "^3.3.3", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", @@ -1662,18 +1662,6 @@ "node": ">=14" } }, - "node_modules/@profoundlogic/hogan": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@profoundlogic/hogan/-/hogan-3.0.4.tgz", - "integrity": "sha512-pmNVGuooS30Mm7YbZd5T7E5zYVO6D5Ct91sn4T39mUvMUc3sCGridcnhAufL1/Bz2QzAtzEn0agNrdk3+5yWzw==", - "license": "Apache-2.0", - "dependencies": { - "nopt": "1.0.10" - }, - "bin": { - "hulk": "bin/hulk" - } - }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", @@ -2818,12 +2806,6 @@ "license": "Apache-2.0", "peer": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -3191,6 +3173,11 @@ "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3300,30 +3287,10 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff2html": { - "version": "3.4.56", - "resolved": "https://registry.npmjs.org/diff2html/-/diff2html-3.4.56.tgz", - "integrity": "sha512-u9gfn+BlbHcyO7vItCIC4z49LJDUt31tODzOfAuJ5R1E7IdlRL6KjugcB9zOpejD+XiR+dDZbsnHSQ3g6A/u8A==", - "license": "MIT", - "dependencies": { - "@profoundlogic/hogan": "^3.0.4", - "diff": "^8.0.3" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "highlight.js": "11.11.1" - } + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" }, "node_modules/dotenv": { "version": "16.6.1", @@ -3538,6 +3505,11 @@ "node": ">=6.9.0" } }, + "node_modules/gitdiff-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/gitdiff-parser/-/gitdiff-parser-0.3.1.tgz", + "integrity": "sha512-YQJnY8aew65id8okGxKCksH3efDCJ9HzV7M9rsvd65habf39Pkh4cgYJ27AaoDMqo1X98pgNJhNMrm/kpV7UVQ==" + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -3612,16 +3584,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -3821,6 +3783,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -3831,6 +3798,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4939,21 +4917,6 @@ "node": ">=18" } }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "license": "MIT", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5123,6 +5086,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-diff-view": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/react-diff-view/-/react-diff-view-3.3.3.tgz", + "integrity": "sha512-CPveApk6n7ZbkW7T6PoptR7LWAvD9hohTHZ7WnKnu3GZkTfUB5rvg486apPo94iYVi4fZd3Nt+rtBZ5877exoQ==", + "dependencies": { + "classnames": "^2.3.2", + "diff-match-patch": "^1.0.5", + "gitdiff-parser": "^0.3.1", + "lodash": "^4.17.21", + "shallow-equal": "^3.1.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/react-dom": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", @@ -5353,6 +5332,11 @@ "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", "license": "MIT" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==" + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -5779,6 +5763,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", diff --git a/services/hackbot-ui/package.json b/services/hackbot-ui/package.json index eacf175a71..5313e58134 100644 --- a/services/hackbot-ui/package.json +++ b/services/hackbot-ui/package.json @@ -13,9 +13,9 @@ "dependencies": { "@sentry/nextjs": "^10.74.0", "better-auth": "^1.6.22", - "diff2html": "^3.4.56", "next": "^15.5.22", "react": "^19.0.0", + "react-diff-view": "^3.3.3", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1",