From 17a5a583ca37ddb72fc18dab352c672fe9874717 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Fri, 16 Oct 2026 16:24:59 -0400 Subject: [PATCH] fix(pr-review-canvas): preserve header-like source lines inside hunks Signed-off-by: Rudy Celekli --- .../skills/pr-review-canvas/renderer.js | 7 ++- .../tests/header-content.test.cjs | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 cursor-team-kit/skills/pr-review-canvas/tests/header-content.test.cjs diff --git a/cursor-team-kit/skills/pr-review-canvas/renderer.js b/cursor-team-kit/skills/pr-review-canvas/renderer.js index 8bdf1b710..8ef3a0d25 100644 --- a/cursor-team-kit/skills/pr-review-canvas/renderer.js +++ b/cursor-team-kit/skills/pr-review-canvas/renderer.js @@ -114,11 +114,14 @@ function renderDiff(target, diffInput) { } var dels = [], adds = [], parsed = []; - var oL = 0, nL = 0, pD = false, pA = false; + var oL = 0, nL = 0, pD = false, pA = false, inHunk = false; for (var pi = 0; pi < wsOut.length; pi++) { var line = wsOut[pi]; - if (line.startsWith('--- ') || line.startsWith('+++ ') || line.startsWith('diff ')) continue; + if (line.startsWith('diff ')) { inHunk = false; continue; } + // Within a hunk these prefixes are source text, e.g. -- count; / ++ count;. + if (!inHunk && (line.startsWith('--- ') || line.startsWith('+++ '))) continue; if (line.startsWith('@@')) { + inHunk = true; var hm = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)/); if (hm) { oL = parseInt(hm[1]); nL = parseInt(hm[2]); } parsed.push({ type: 'hunk', text: line }); pD = false; pA = false; continue; diff --git a/cursor-team-kit/skills/pr-review-canvas/tests/header-content.test.cjs b/cursor-team-kit/skills/pr-review-canvas/tests/header-content.test.cjs new file mode 100644 index 000000000..410816ab8 --- /dev/null +++ b/cursor-team-kit/skills/pr-review-canvas/tests/header-content.test.cjs @@ -0,0 +1,48 @@ +const assert = require('node:assert/strict'); +const { execFileSync } = require('node:child_process'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const test = require('node:test'); +const vm = require('node:vm'); + +function render(patch, fullDiff = false) { + const context = { document: { addEventListener() {} } }; + vm.createContext(context); + vm.runInContext(fs.readFileSync(path.join(__dirname, '../renderer.js'), 'utf8'), context); + const target = { innerHTML: '' }; + // GitHub's files API exposes the hunk portion of git diff as `patch`. + context.renderDiff(target, fullDiff ? patch : patch.slice(patch.indexOf('@@'))); + return target.innerHTML; +} + +function patchFor(before, after) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'canvas-newline-')); + try { + fs.writeFileSync(path.join(dir, 'before'), before); + fs.writeFileSync(path.join(dir, 'after'), after); + try { + return execFileSync('git', ['diff', '--no-index', '--no-color', '--', 'before', 'after'], { cwd: dir, encoding: 'utf8' }); + } catch (error) { + if (error.status !== 1) throw error; + return error.stdout; + } + } finally { + fs.rmSync(dir, { recursive: true }); + } +} + +for (const fullDiff of [false, true]) { + test(`preserve increment/decrement code in ${fullDiff ? 'full diff' : 'API patch'}`, () => { + const html = render(patchFor('-- count;\n', '++ count;\n'), fullDiff); + assert.match(html, /class="diff-del">1<\/td><\/td>-- count;<\/td>/); + assert.match(html, /class="diff-add"><\/td>1<\/td>\+\+ count;<\/td>/); + assert.doesNotMatch(html, /diff-code">[+-]{2} [ab]\/(?:before|after)/); + }); +} + +test('ordinary changed lines remain visible', () => { + const html = render(patchFor('old\n', 'new\n')); + assert.match(html, /diff-code">old<\/td>/); + assert.match(html, /diff-code">new<\/td>/); +});