feat: add image diff viewer with classic, highlight, and slider modes#412
feat: add image diff viewer with classic, highlight, and slider modes#412
Conversation
…, and slider modes Replace the generic "Binary file" placeholder with a rich image diff viewer for png, jpg, jpeg, gif, and webp files. The backend now detects image files by extension, base64-encodes them (up to 5 MB), and sends them as a new ImageBase64 FileContent variant. The frontend renders three viewing modes: side-by-side comparison, pixel-difference highlighting via Canvas API, and a draggable slider overlay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e when single image - Rename "Side-by-side" mode to "Classic" - Center the mode toolbar buttons - Hide the toolbar entirely when only one side exists (add or delete) - Remove the bottom border from the toolbar Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lider handle - Add draggable="false" to slider images to prevent browser image drag and text selection during slider interaction - Replace dash icon (- -) with left/right arrow triangles on the slider handle for a clearer resize affordance Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the slider divider from inside the aspect-ratio-constrained image container to the full-height slider container, and remove vertical padding in slider mode so the line extends from the toolbar to the bottom of the viewer area. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep images constrained to 70vh while the divider line extends to the full container height. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
builderbot/crates/git-diff/src/files.rs
Lines 165 to 170 in 7d43c0b
get_file_at_ref() now tries to classify git show output as image data, but cli::run() in crates/git-diff/src/cli.rs decodes stdout with String::from_utf8 first. For any committed image blob (HEAD:path/to/foo.png, older revisions, etc.), this call returns InvalidUtf8 before is_binary()/bytes_to_image_or_binary() run, so reference-image loading still only works from WORKDIR and fails for normal committed refs in Differ/Staged.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| Err(e) if is_utf8_parse_error(&e) => Ok(Some(git::File { | ||
| path: path.to_string(), |
There was a problem hiding this comment.
Handle non-UTF8 remote image blobs without downgrading them
For remote branches, run_remote_git() ultimately returns a String, so git show <ref>:<image> fails before file_content_from_text() ever sees the raw bytes. This fallback turns every non-UTF8 PNG/JPEG/GIF/WebP into plain Binary, which means the new image viewer never works for remote image diffs or remote reference files in Staged; those cases still render as the old binary notice.
Useful? React with 👍 / 👎.
| export function isImageDiff(diff: FileDiff): boolean { | ||
| const beforeImage = diff.before?.content.type === 'ImageBase64'; | ||
| const afterImage = diff.after?.content.type === 'ImageBase64'; | ||
| return beforeImage || afterImage; |
There was a problem hiding this comment.
Treat partially previewable image diffs as binary, not add/delete
This helper returns true when either side is ImageBase64, even if the other side exists but fell back to Binary (for example because it exceeded IMAGE_PREVIEW_MAX_BYTES). In that case DiffViewer takes the image branch and ImageDiffViewer shows the missing side as “No previous version”/“File deleted”, which misrepresents modified large images as adds or deletes instead of showing a binary-diff fallback.
Useful? React with 👍 / 👎.
When viewing diffs for remote branches (Blox workspace projects), `git show <ref>:<path>` returns binary data for image files. The previous `ws_exec` / `run_workspace_git` path called `String::from_utf8` on the raw stdout, failing silently with a `Binary` fallback instead of an `ImageBase64` preview. Fix by adding a `ws_exec_bytes` variant in blox-cli (and wiring it through blox.rs / branches.rs) that returns `Vec<u8>` without UTF-8 validation. `load_remote_file_at_ref` in diff_commands.rs now uses this bytes path for `git show`, then passes the raw bytes through the existing `file_content_from_bytes` helper, which detects binary content, checks for a known image extension, and base64-encodes the data into an `ImageBase64` FileContent — the same representation used for local worktree branches. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Image.diff.mov
Summary
ImageDiffViewercomponent with three viewing modes: classic (side-by-side), highlight (overlay with differences emphasized), and slider (interactive before/after comparison)DiffViewercomponent, automatically activating for image file changesTest plan
🤖 Generated with Claude Code