Polish diff view hunk controls

This commit is contained in:
Bohdan Triapitsyn
2026-06-14 00:03:02 +03:00
parent 876738229f
commit 8b318d2776
6 changed files with 122 additions and 20 deletions
+10 -6
View File
@@ -99,7 +99,7 @@
},
"packages/electron": {
"name": "@openchamber/electron",
"version": "1.12.3",
"version": "1.12.4",
"dependencies": {
"@openchamber/web": "workspace:*",
"electron-context-menu": "^4.1.2",
@@ -114,7 +114,7 @@
},
"packages/ui": {
"name": "@openchamber/ui",
"version": "1.12.3",
"version": "1.12.4",
"dependencies": {
"@base-ui/react": "^1.4.0",
"@codemirror/autocomplete": "^6.20.0",
@@ -146,7 +146,7 @@
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
"@opencode-ai/sdk": "^1.17.0",
"@pierre/diffs": "1.1.0-beta.13",
"@pierre/diffs": "1.3.0-beta.4",
"@replit/codemirror-vim": "^6.3.0",
"@simplewebauthn/browser": "13.3.0",
"@tanstack/react-virtual": "^3.13.18",
@@ -216,7 +216,7 @@
},
"packages/vscode": {
"name": "openchamber",
"version": "1.12.3",
"version": "1.12.4",
"dependencies": {
"@openchamber/ui": "workspace:*",
"@opencode-ai/sdk": "^1.17.0",
@@ -239,7 +239,7 @@
},
"packages/web": {
"name": "@openchamber/web",
"version": "1.12.3",
"version": "1.12.4",
"bin": {
"openchamber": "./bin/cli.js",
},
@@ -969,7 +969,11 @@
"@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="],
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.13", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-D35rxDu5V7XHX5aVGU6PF12GhscL+I+9QYgxK/i3h0d2XSirAxDdVNm49aYwlOhgmdvL0NbS1IHxPswVB5yJvw=="],
"@pierre/diffs": ["@pierre/diffs@1.3.0-beta.4", "", { "dependencies": { "@pierre/theme": "1.0.3", "@pierre/theming": "0.0.1", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-poFcsvhcQt9lH/InzAPaGs47WYHMidnFCjuYGNU41HiVLJP4mkIQDSdvcnPIkBuh/cYbPOQg/YE3T1kSpr01GA=="],
"@pierre/theme": ["@pierre/theme@1.0.3", "", {}, "sha512-sWHv11TMoqKxKDgTIk5VbhQjdPhs8DCcBxbjh3mRlS3YOM/OcrWoGX6MM8eBGn9cUu3M46Py0JnxsG2nJaFTuA=="],
"@pierre/theming": ["@pierre/theming@0.0.1", "", { "peerDependencies": { "@pierre/theme": "^1.0.0", "@shikijs/themes": "^3.0.0 || ^4.0.0", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0", "shiki": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@pierre/theme", "@shikijs/themes", "react", "react-dom", "shiki"] }, "sha512-1thlEtJbqdyLzc1ZS2KQa1q7FzDGHT4dTEdKHoyQjOMeWWOmbVG5/ndEfOKfAb5Fzkz8cNJrOjFLiZoDH/A03A=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
+1 -1
View File
@@ -41,7 +41,7 @@
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
"@opencode-ai/sdk": "^1.17.0",
"@pierre/diffs": "1.1.0-beta.13",
"@pierre/diffs": "1.3.0-beta.4",
"@replit/codemirror-vim": "^6.3.0",
"@simplewebauthn/browser": "13.3.0",
"@tanstack/react-virtual": "^3.13.18",
@@ -119,7 +119,7 @@ const TOOL_DIFF_METRICS = {
lineHeight: 24,
diffHeaderHeight: 44,
hunkSeparatorHeight: 24,
fileGap: 0,
spacing: 0,
};
const usePierreThemeConfig = (): PierreThemeConfig => {
@@ -1388,7 +1388,7 @@ const TOOL_DIFF_METRICS = {
lineHeight: 24,
diffHeaderHeight: 44,
hunkSeparatorHeight: 24,
fileGap: 0,
spacing: 0,
};
const TOOL_COLLAPSED_CUSTOM_STYLE: React.CSSProperties = {
@@ -599,15 +599,15 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
return (
<div ref={setSectionRef} className="scroll-mt-9 border-b border-[var(--interactive-border)]/40 last:border-b-0">
<div className="sticky top-0 z-10 bg-[var(--surface-muted)]">
<div className="sticky top-0 z-10 border-b border-[var(--interactive-border)]/35 bg-[var(--surface-elevated)]/90 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--surface-elevated)]/80">
<button
type="button"
onClick={handleToggle}
className={cn(
'group/header relative flex w-full items-center gap-2 px-3 py-1.5 overflow-hidden',
'group/header relative flex min-h-9 w-full items-center gap-2 overflow-hidden px-3 py-2',
'bg-transparent',
'text-muted-foreground hover:text-foreground',
isSelected ? 'bg-[var(--interactive-selection)]/40' : null
isSelected ? 'bg-[var(--interactive-selection)]/35' : null
)}
>
<div className="absolute inset-0 pointer-events-none group-hover/header:bg-[var(--interactive-hover)]/50" />
@@ -91,6 +91,104 @@ const WEBKIT_SCROLL_FIX_CSS = `
height: 24px !important;
}
}
[data-separator="line-info-basic"] {
height: 24px !important;
background: var(--diffs-bg) !important;
position: relative;
}
[data-diff-type="single"] [data-gutter],
[data-diff-type="split"] [data-deletions] [data-gutter] {
[data-separator-wrapper] {
position: absolute;
left: 100%;
display: flex;
align-items: center;
gap: unset;
width: max-content;
background: transparent;
color: var(--diffs-fg-number);
font-family: var(--diffs-header-font-family, var(--font-sans));
font-size: 0.75rem;
line-height: 1;
margin-left: calc(-2ch - 2px);
}
[data-separator-wrapper][data-separator-multi-button] {
margin-left: calc(-3ch - 2px);
}
[data-expand-button],
[data-separator-content] {
display: block;
align-self: unset;
min-width: unset;
min-height: unset;
padding: 0;
flex-shrink: 0;
grid-column: unset;
border: none;
width: auto;
height: auto;
background-color: transparent;
color: inherit;
font: inherit;
}
[data-expand-button]:not([data-expand-all-button]) {
&[data-expand-down]::before {
content: '\\2191';
}
&[data-expand-up]::before {
content: '\\2193';
}
&[data-expand-both]::before {
content: '\\2195';
}
svg {
display: none;
}
}
[data-separator-content] {
background: transparent;
margin-left: calc(2px + 1ch);
}
[data-expand-all-button] {
position: relative;
margin-left: 14px;
text-transform: lowercase;
}
[data-expand-all-button]::before {
content: '';
display: block;
position: absolute;
top: 50%;
left: -8px;
margin-top: -1px;
width: 3px;
height: 3px;
border-radius: 2px;
background-color: var(--diffs-fg-number);
pointer-events: none;
}
[data-separator-content]:hover,
[data-expand-button]:hover,
[data-expand-all-button]:hover {
color: var(--diffs-fg);
}
[data-expand-all-button]:hover {
text-decoration: underline;
}
}
`;
// Fast cache key - use length + samples instead of full hash
@@ -267,7 +365,7 @@ const virtualizerCache = new WeakMap<Document | HTMLElement, VirtualizerEntry>()
const VIRTUAL_METRICS: Partial<VirtualFileMetrics> = {
lineHeight: 24,
hunkSeparatorHeight: 24,
fileGap: 0,
spacing: 0,
};
function resolveVirtualizerTarget(container: HTMLElement): VirtualizerTarget {
@@ -324,7 +422,7 @@ function acquireSharedVirtualizer(container: HTMLElement): SharedVirtualizer | n
}
const wakeVirtualizer = (
instance: PierreFileDiff<unknown>,
instance: PierreFileDiff<PierreAnnotationData>,
sharedVirtualizer: SharedVirtualizer | null,
forceUpdate: () => void,
): (() => void) => {
@@ -545,11 +643,11 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
const diffRootRef = useRef<HTMLDivElement | null>(null);
const diffContainerRef = useRef<HTMLDivElement | null>(null);
const diffInstanceRef = useRef<PierreFileDiff<unknown> | null>(null);
const diffInstanceRef = useRef<PierreFileDiff<PierreAnnotationData> | null>(null);
const sharedVirtualizerRef = useRef<SharedVirtualizer | null>(null);
const instanceVirtualizerRef = useRef<Virtualizer | null>(null);
const instanceWorkerPoolRef = useRef<unknown>(null);
const instanceVirtualHunkSeparatorsRef = useRef<FileDiffOptions<unknown>['hunkSeparators'] | undefined>(undefined);
const instanceVirtualHunkSeparatorsRef = useRef<FileDiffOptions<PierreAnnotationData>['hunkSeparators'] | undefined>(undefined);
const instanceFileDiffRef = useRef<FileDiffMetadata | undefined>(undefined);
const instanceOldFileRef = useRef<FileContents | undefined>(undefined);
const instanceNewFileRef = useRef<FileContents | undefined>(undefined);
@@ -755,17 +853,17 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
: false;
if (!instance) {
instance = sharedVirtualizer
? new VirtualizedFileDiff(
options as unknown as FileDiffOptions<unknown>,
? new VirtualizedFileDiff<PierreAnnotationData>(
options as FileDiffOptions<PierreAnnotationData>,
sharedVirtualizer.virtualizer,
VIRTUAL_METRICS,
workerPool,
)
: new PierreFileDiff(options as unknown as FileDiffOptions<unknown>, workerPool);
: new PierreFileDiff(options as FileDiffOptions<PierreAnnotationData>, workerPool);
diffInstanceRef.current = instance;
lastAppliedSelectionRef.current = null;
} else {
instance.setOptions(options as unknown as FileDiffOptions<unknown>);
instance.setOptions(options as FileDiffOptions<PierreAnnotationData>);
}
instanceVirtualizerRef.current = virtualizer;