fix(app): stabilize virtual session timeline interactions (#28422)

This commit is contained in:
Luke Parker
2026-05-25 12:05:27 +10:00
committed by GitHub
parent 9495ecd536
commit f023c63a60
14 changed files with 1062 additions and 177 deletions
+49 -84
View File
@@ -1,5 +1,5 @@
import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"
import { formatPatch, parsePatch, structuredPatch } from "diff"
import { parsePatch } from "diff"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
type LegacyDiff = {
@@ -14,107 +14,35 @@ type LegacyDiff = {
type SnapshotDiff = SnapshotFileDiff & { file: string }
type ReviewDiff = SnapshotDiff | VcsFileDiff | LegacyDiff
export type DiffSource = Pick<LegacyDiff, "file" | "patch" | "before" | "after">
export type ViewDiff = {
file: string
patch: string
additions: number
deletions: number
status?: "added" | "deleted" | "modified"
fileDiff: FileDiffMetadata
}
const cache = new Map<string, FileDiffMetadata>()
const diffCacheLimit = 16
const patchFileDiffCache = new Map<string, FileDiffMetadata>()
function patch(diff: ReviewDiff) {
if (typeof diff.patch === "string") {
try {
const [patch] = parsePatch(diff.patch)
const beforeLines: Array<{ text: string; newline: boolean }> = []
const afterLines: Array<{ text: string; newline: boolean }> = []
let previous: "-" | "+" | " " | undefined
const patchIsPartial = patch.hunks.every((h) => h.oldStart > 1)
for (const hunk of patch.hunks) {
for (const line of hunk.lines) {
if (line.startsWith("\\")) {
if (previous === "-" || previous === " ") {
const before = beforeLines.at(-1)
if (before) before.newline = false
}
if (previous === "+" || previous === " ") {
const after = afterLines.at(-1)
if (after) after.newline = false
}
continue
}
if (line.startsWith("-")) {
beforeLines.push({ text: line.slice(1), newline: true })
previous = "-"
} else if (line.startsWith("+")) {
afterLines.push({ text: line.slice(1), newline: true })
previous = "+"
} else {
// context line (starts with ' ')
beforeLines.push({ text: line.slice(1), newline: true })
afterLines.push({ text: line.slice(1), newline: true })
previous = " "
}
}
}
return {
before: beforeLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""),
after: afterLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""),
patch: diff.patch,
patchIsPartial,
}
} catch {
return { before: "", after: "", patch: diff.patch, patchIsPartial: false }
}
}
return {
before: "before" in diff && typeof diff.before === "string" ? diff.before : "",
after: "after" in diff && typeof diff.after === "string" ? diff.after : "",
patch: formatPatch(
structuredPatch(
diff.file,
diff.file,
"before" in diff && typeof diff.before === "string" ? diff.before : "",
"after" in diff && typeof diff.after === "string" ? diff.after : "",
"",
"",
{ context: Number.MAX_SAFE_INTEGER },
),
),
patchIsPartial: false,
}
}
function file(file: string, patch: string, before: string, after: string, partial = false) {
const hit = cache.get(patch)
if (hit) return hit
let value: FileDiffMetadata | undefined
if (partial) value = parsePatchFiles(patch)[0]?.files[0]
if (value === undefined) value = parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })
cache.set(patch, value)
return value
export function resolveFileDiff(diff: DiffSource) {
if (typeof diff.patch === "string") return fileDiffFromPatch(diff.file, diff.patch)
return fileDiffFromContent(
diff.file,
typeof diff.before === "string" ? diff.before : "",
typeof diff.after === "string" ? diff.after : "",
)
}
export function normalize(diff: ReviewDiff): ViewDiff {
const next = patch(diff)
const fileDiff = file(diff.file, next.patch, next.before, next.after, next.patchIsPartial)
return {
file: diff.file,
patch: next.patch,
additions: diff.additions,
deletions: diff.deletions,
status: diff.status,
fileDiff,
fileDiff: resolveFileDiff(diff),
}
}
@@ -122,3 +50,40 @@ export function text(diff: ViewDiff, side: "deletions" | "additions") {
if (side === "deletions") return diff.fileDiff.deletionLines.join("")
return diff.fileDiff.additionLines.join("")
}
function fileDiffFromPatch(file: string, patch: string) {
const key = `${file}\0${patch}`
const hit = patchFileDiffCache.get(key)
if (hit) {
patchFileDiffCache.delete(key)
patchFileDiffCache.set(key, hit)
return hit
}
const input = patchInput(file, patch)
const value = (input ? parsePatchFiles(input)[0]?.files[0] : undefined) ?? emptyFileDiff(file)
patchFileDiffCache.set(key, value)
while (patchFileDiffCache.size > diffCacheLimit) patchFileDiffCache.delete(patchFileDiffCache.keys().next().value!)
return value
}
function patchInput(file: string, patch: string) {
try {
const parsed = parsePatch(patch)[0]
if (!parsed) return
if (parsed.index || parsed.oldFileName || parsed.newFileName) return patch
if (!parsed.hunks.length) return
return `Index: ${file}\n===================================================================\n--- ${file}\t\n+++ ${file}\t\n${patch}`
} catch {
return
}
}
function fileDiffFromContent(file: string, before: string, after: string) {
if (!before && !after) return emptyFileDiff(file)
return parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })
}
function emptyFileDiff(file: string) {
return parseDiffFromFile({ name: file, contents: "" }, { name: file, contents: "" })
}