fix(site): upgrade @pierre/diffs to 1.1.0-beta.19 (#22895)

Fixes a race condition in `DiffHunksRenderer` where a stale async
highlight callback overwrites the render cache with an old diff, causing
a hunk count mismatch:

```
DiffHunksRenderer.renderHunks: lineHunk doesn't exist
```

## Root cause

The `DiffHunksRenderer` in `@pierre/diffs@1.0.11` caches highlighted AST
results keyed by diff object reference. When the shiki highlighter isn't
fully loaded, it fires `asyncHighlight(diff)` which captures the current
diff in a closure. If the diff changes before that promise resolves,
`onHighlightSuccess` unconditionally overwrites `renderCache` with the
stale diff/result pair. The subsequent `rerender()` then iterates the
new diff's hunks against the old result's `code.hunks` array, crashing
at an out-of-bounds index.

## Fix

Upgrades `@pierre/diffs` from `1.0.11` to `1.1.0-beta.19`, which
completely refactors the rendering pipeline:

- Replaces the per-hunk `code.hunks[hunkIndex]` lookup with flat
`additionLines`/`deletionLines` arrays indexed directly by line index
- Uses a new `iterateOverDiff` callback pattern instead of the
`renderHunks` method
- The `lineHunk doesn't exist` error is gone from the codebase entirely

The only code change on our side is adapting `extractDiffContent()` in
`FilesChangedPanel.tsx` to the new `ChangeContent`/`ContextContent`
types where `deletions`, `additions`, and `lines` are now counts with
index pointers into top-level
`FileDiffMetadata.deletionLines`/`additionLines` arrays.
This commit is contained in:
Kyle Carberry
2026-03-10 14:18:42 +00:00
committed by GitHub
parent 8c70170ee7
commit ba764a24ea
3 changed files with 28 additions and 16 deletions
+1 -1
View File
@@ -53,7 +53,7 @@
"@mui/material": "5.18.0",
"@mui/system": "5.18.0",
"@mui/x-tree-view": "7.29.10",
"@pierre/diffs": "1.0.11",
"@pierre/diffs": "1.1.0-beta.19",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
+12 -7
View File
@@ -74,8 +74,8 @@ importers:
specifier: 7.29.10
version: 7.29.10(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2)
'@pierre/diffs':
specifier: 1.0.11
version: 1.0.11(react-dom@19.2.2(react@19.2.2))(react@19.2.2)
specifier: 1.1.0-beta.19
version: 1.1.0-beta.19(react-dom@19.2.2(react@19.2.2))(react@19.2.2)
'@radix-ui/react-avatar':
specifier: 1.1.11
version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2)
@@ -1596,12 +1596,16 @@ packages:
cpu: [x64]
os: [win32]
'@pierre/diffs@1.0.11':
resolution: {integrity: sha512-j6zIEoyImQy1HfcJqbrDwP0O5I7V2VNXAaw53FqQ+SykRfaNwABeZHs9uibXO4supaXPmTx6LEH9Lffr03e1Tw==, tarball: https://registry.npmjs.org/@pierre/diffs/-/diffs-1.0.11.tgz}
'@pierre/diffs@1.1.0-beta.19':
resolution: {integrity: sha512-XxGPKkVW+1t2KJQfgjmSnS+93nI9+ACJl1XjhF3Lo4BdQJOxV3pHeyix31ySn/m/1llq6O/7bXucE0OYCK6Kog==, tarball: https://registry.npmjs.org/@pierre/diffs/-/diffs-1.1.0-beta.19.tgz}
peerDependencies:
react: ^18.3.1 || ^19.0.0
react-dom: ^18.3.1 || ^19.0.0
'@pierre/theme@0.0.22':
resolution: {integrity: sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA==, tarball: https://registry.npmjs.org/@pierre/theme/-/theme-0.0.22.tgz}
engines: {vscode: ^1.0.0}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz}
engines: {node: '>=14'}
@@ -7941,10 +7945,9 @@ snapshots:
'@oxc-resolver/binding-win32-x64-msvc@11.14.0':
optional: true
'@pierre/diffs@1.0.11(react-dom@19.2.2(react@19.2.2))(react@19.2.2)':
'@pierre/diffs@1.1.0-beta.19(react-dom@19.2.2(react@19.2.2))(react@19.2.2)':
dependencies:
'@shikijs/core': 3.22.0
'@shikijs/engine-javascript': 3.22.0
'@pierre/theme': 0.0.22
'@shikijs/transformers': 3.22.0
diff: 8.0.3
hast-util-to-html: 9.0.5
@@ -7953,6 +7956,8 @@ snapshots:
react-dom: 19.2.2(react@19.2.2)
shiki: 3.22.0
'@pierre/theme@0.0.22': {}
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -89,6 +89,7 @@ function extractDiffContent(
const file = parsedFiles.find((f) => f.name === fileName);
if (!file) return "";
const lines = side === "additions" ? file.additionLines : file.deletionLines;
const collected: string[] = [];
for (const hunk of file.hunks) {
let addLine = hunk.additionStart;
@@ -96,10 +97,14 @@ function extractDiffContent(
for (const block of hunk.hunkContent) {
if (block.type === "context") {
for (const line of block.lines) {
for (let i = 0; i < block.lines; i++) {
const ln = side === "additions" ? addLine : delLine;
if (ln >= startLine && ln <= endLine) {
collected.push(line);
const idx =
side === "additions"
? block.additionLineIndex + i
: block.deletionLineIndex + i;
if (lines[idx] != null) collected.push(lines[idx]);
}
addLine++;
delLine++;
@@ -107,23 +112,25 @@ function extractDiffContent(
} else {
// ChangeContent block.
if (side === "deletions") {
for (const line of block.deletions) {
for (let i = 0; i < block.deletions; i++) {
if (delLine >= startLine && delLine <= endLine) {
collected.push(line);
const line = lines[block.deletionLineIndex + i];
if (line != null) collected.push(line);
}
delLine++;
}
// Addition lines in a change block still advance
// the addition counter.
addLine += block.additions.length;
addLine += block.additions;
} else {
// side === "additions"
// Deletion lines in a change block still advance
// the deletion counter.
delLine += block.deletions.length;
for (const line of block.additions) {
delLine += block.deletions;
for (let i = 0; i < block.additions; i++) {
if (addLine >= startLine && addLine <= endLine) {
collected.push(line);
const line = lines[block.additionLineIndex + i];
if (line != null) collected.push(line);
}
addLine++;
}