* Add SuggestionBlock type definition
Add TypeScript interface for parsed suggestion block data structure
containing original code, suggested code, language, and position info.
* Add suggestion block parsing utilities
- hasSuggestionBlock: Check if text contains suggestion block
- parseSuggestionBlocks: Parse suggestion blocks with original code
- createSuggestionTemplate: Generate suggestion template for editor
- extractFirstSuggestion: Extract first suggestion code from body
- formatSuggestionForPrompt: Format suggestion for AI prompt output
Includes comprehensive test coverage (20 tests).
* Add suggestion button to CommentForm
- Add selectedCode and language props
- Add toolbar with "Add suggestion" button
- Button inserts suggestion template into textarea
- Button disabled when no code is selected
* Pass selected code content to CommentForm
- Add getSelectedCodeContent() to extract code from selected lines
- Pass selectedCode and language props to CommentForm
- Include codeContent when adding comments for suggestion support
* Add GitHub-style diff view for suggestion blocks
- Add SuggestionBlockRenderer component
- Display original code with red background and "-" prefix
- Display suggested code with green background and "+" prefix
- Parse suggestion blocks using suggestionUtils
* Add structured prompt output for suggestions
- Format suggestions with ORIGINAL/SUGGESTED blocks for AI prompts
- Extract context text before suggestion blocks
- Add tests for suggestion prompt formatting
* Fix type assertions and improve code quality
- Replace type assertions with instanceof checks in SideBySideDiffChunk
- Fix language detection to use file path instead of code content
- Simplify hasSuggestionBlock regex without global flag
* Fix missing codeContent in suggestion prompt output
Pass codeSnapshot.content to formatCommentPrompt so that ORIGINAL
block is included in the generated prompt for suggestion comments.
* Address PR #199 review feedback
- Remove dead code: formatSuggestionForPrompt, extractFirstSuggestion
- Remove unused language parameter from createSuggestionTemplate
- Remove language prop plumbing from DiffChunk/SideBySideDiffChunk/CommentForm
- Localize SuggestionBlock type to suggestionUtils module
- Make parseSuggestionBlocks a pure body-only parser
- Handle all suggestion blocks in prompt output (not just the first)
- Extract SuggestionDiffLine component for suggestion diff rendering
- Fix newline insertion position in CommentForm suggestion template
* feat: add expand code context functionality
- Add ExpandButton component for expanding hidden lines in diffs
- Add useExpandedLines hook to manage expanded state and fetch file content
- Support expanding lines up/down with 20-line increments or expand all
- Merge adjacent chunks when fully connected to eliminate visual gaps
- Show expand buttons at file start, between chunks, and at file end
- Hide chunk header when file displays from line 1 after full expansion
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve expand all bug when mixing up/down directions
Bug fix:
- Fixed expandAllBetweenChunks to consider both 'up' direction from current
chunk and 'down' direction from previous chunk when calculating total
expanded lines. This fixes the issue where clicking "Expand All" after
partial expansion would reset the count instead of accumulating.
Code improvements:
- Add useMemo for mergedChunks to avoid recalculation on every render
- Use useRef for state tracking to prevent dependency loops in ensureFileContent
- Add React.memo wrapper to ExpandButton for performance
- Add ARIA labels for accessibility (aria-label, aria-busy, aria-hidden)
- Add type guard isExpandedLine and utility getSideBySideLineClass
- Extract shouldShowHeaderAtStart and renderExpandButton helper functions
- Fix isConnectedToPrevious prop (was hardcoded to false)
- Use isConnectedToPrevious for conditional border styling in DiffChunk
- Add JSDoc for getHiddenLinesAfter explaining return values
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: swap expand button icons for intuitive direction
Swap ChevronUp/ChevronDown icons so arrows indicate where content
will appear rather than which direction to expand from.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prefetch file content so bottom expand button shows correctly
Previously the bottom expand button was hidden on initial render because
the file's total line count was unknown (hiddenLinesAfter returned -1).
Now each DiffViewer prefetches file content on mount, so the actual
remaining line count is available immediately and the button renders
with correct "Expand N lines" / "Expand all" options from the start.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: support drag-to-expand on expand header bars
Add mouse drag support on expand header containers to expand code
context line by line. Dragging down expands the upper chunk and
dragging up expands the lower chunk.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "feat: support drag-to-expand on expand header bars"
This reverts commit 99647a688e.
* fix: expand navigation and prefetch performance improvements
- Fix expand header showing on added/deleted files (no hidden lines exist)
- Fix j/k navigation skipping expanded lines by lifting useExpandedLines
to App level so navigation and rendering share the same merged chunks
- Add lightweight /api/line-count endpoint to avoid prefetching full file
content just for total line counts
- Lazy-load line counts via IntersectionObserver (200px ahead) and skip
collapsed/image/added/deleted files
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: garrick.zhang <garrick.zhang@bytedance.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add color-scheme property to data-theme selectors to make browser native
UI elements (scrollbars, form inputs, etc.) respect the app's theme setting.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
In a git worktree, .git is a file (not a directory) containing a
reference to the actual git directory. The file watcher was failing
to watch changes because it tried to watch the .git file path directly.
This change uses `git rev-parse --git-dir` via simple-git to resolve
the actual git directory path, which correctly handles:
- Normal repositories (.git directory)
- Git worktrees (.git file with gitdir reference)
- Submodules
Fixes file change detection in `difit staged` mode when running
inside a git worktree.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: Add revision selector to change base/target commits dynamically
Add dropdown selectors to allow users to change the base and target revisions
without restarting the server.
Changes:
- Add /api/revisions endpoint to fetch available branches and recent commits
- Modify /api/diff to accept base/target query parameters
- Add RevisionSelector component with optgroups (Special, Recent Commits, Branches)
- Add revision state management in App.tsx with confirmation dialog for clearing comments
- Handle working/staged constraints (working can only compare with staged)
- Update test mocks to handle multiple API endpoints
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: Resolve HEAD/HEAD^ to commit hashes for revision selector
When starting without arguments (default HEAD), the revision selector was
showing "All Uncommitted Changes" for both base and target because HEAD and
HEAD^ values didn't exist in the dropdown options.
Changes:
- Add resolvedBase and resolvedTarget to getRevisionOptions() to resolve
symbolic refs like HEAD/HEAD^ to actual commit hashes
- Update /api/revisions to return resolved values
- Use resolved values as initial selection in revision selector
- This ensures the correct commits are selected on initial load
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* fix: Resolve symbolic refs in /api/diff response for revision selector
The previous fix only resolved refs in /api/revisions, but /api/diff was
still returning HEAD/HEAD^ which would overwrite the resolved values.
Changes:
- Add resolveCommitish() method to GitDiffParser
- Resolve symbolic refs (HEAD, HEAD^, etc.) in /api/diff response
- Use the new method in getRevisionOptions() to avoid duplication
Now both /api/diff and /api/revisions return resolved commit hashes,
ensuring the revision selector displays the correct values.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* fix: Use shortHash for select values to match resolved hashes
The select options were using full commit hashes (commit.hash) as values,
but the server was returning 7-character short hashes. This caused the
selector to not match and default to the first option.
Changes:
- Change RevisionSelector to use commit.shortHash as option values
- Remove debug logging
Now the selector correctly matches the resolved short hashes returned
from the server.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* feat: Sort branches with default branch first in revision selector
Add getDefaultBranch() method to detect the repository's default branch
(usually main or master) by checking origin/HEAD, and sort the branch list
to show it first.
Changes:
- Add getDefaultBranch() method to GitDiffParser
- Try to get default branch from origin/HEAD
- Fall back to common defaults (main, master) if origin/HEAD not set
- Sort branches in getRevisionOptions():
1. Default branch (main/master)
2. Current branch
3. Others (alphabetically)
This makes it easier to find the main branch in the selector.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* feat: Replace native select with custom dropdown for revision selector
Replace the native select element with a custom dropdown that provides
better visibility and UX:
- Click or hover to open dropdown
- Wider dropdown (320px min) with better spacing
- Full commit messages visible (no truncation in dropdown)
- Section headers for Special/Recent Commits/Branches
- Selected item highlighted
- Auto-closes on mouse leave or outside click
- Uses ChevronDown icon for better affordance
The custom dropdown provides much better readability compared to the
native select, especially for long commit messages and branch names.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* feat: Improve revision selector with floating-ui and UX enhancements
User request: Improve the revision selector in the header to prevent viewport overflow and enhance interaction.
Implemented improvements:
- Install @floating-ui/react dependency
- Update RevisionSelector to use floating-ui for viewport-aware positioning with flip() and shift() middleware
- Replace click interaction with hover/blur using useHover() and useFocus() hooks
- Add safePolygon() to prevent dropdown from closing when moving mouse to dropdown
- Reduce selector trigger width to half (max-w-[100px]) and prevent text wrapping
- Set dropdown width to 360px
- Allow commit messages to wrap in dropdown with break-words class
- Set initial focus to current value when dropdown opens using getInitialFocusIndex()
- Disable react-hooks/refs ESLint rule due to false positives with @floating-ui/react callback refs
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* fix: Remove commit message truncation in revision selector
Remove the 50-character truncation of commit messages in the revision
selector dropdown. Messages are now displayed in full and will wrap
using the break-words CSS class applied in the frontend.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* fix: Make commit message font size consistent with other options
Add text-xs class to commit hash and message to match the font size
used in Special options and Branches sections.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* feat: Disable commits older than base in target selector
Add disabledValues prop to RevisionSelector to support disabling multiple
values. In the target revision selector, disable not only the base revision
but also all commits older than the base revision to prevent selecting
invalid revision ranges.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* refactor: Simplify disabled values by removing disabledValue prop
Replace disabledValue prop with disabledValues array for consistency.
This simplifies the implementation by using a single approach for
disabling options in the revision selector.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
* unclear existing comments
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: decouple file collapse state from viewed state
Add independent collapse state management while maintaining backward-compatible
behavior where marking a file as viewed also collapses it, and unmarking expands it.
Changes:
- Add collapsedFiles state to track collapse state independently
- Add toggleFileCollapsed function for manual collapse/expand
- Update toggleFileReviewed to sync collapse state with viewed state
- Update DiffViewer to accept collapsedFiles and onToggleCollapsed props
- Chevron button now toggles collapse state only
- Viewed button toggles both viewed and collapse states together
- Update tests to use collapsedFiles instead of reviewedFiles for collapse state
This allows users to:
- Collapse files without marking them as viewed
- Keep files expanded even after marking as viewed
Tasks completed:
- Verified current coupling of collapse and viewed states
- Implemented independent collapse state management
- Added viewed state synchronization to maintain UX continuity
- Verified behavior in local environment
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: initialize collapsed files from viewed files on load
Initialize the collapsed files state from viewed files when the page loads,
ensuring that previously viewed files start in a collapsed state.
Implementation:
- Add collapsedInitializedRef to track initialization status
- Initialize collapsedFiles from viewedFiles on first load
- Reset initialization flag when diff context (repositoryId/commit) changes
- Preserve manual collapse/expand operations after initialization
This ensures a consistent UX where viewed files remain collapsed across
page reloads while still allowing independent manual control.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* smooth scroll on file viewed
* feat: add Alt+Click to collapse/expand all files
Add keyboard modifier support to collapse button to allow bulk operations:
- Alt+Click on collapsed file (ChevronRight) expands all files
- Alt+Click on expanded file (ChevronDown) collapses all files
Implementation:
- Update toggleAllFilesCollapsed to accept shouldCollapse parameter
- Check altKey in onClick handler to determine single vs bulk operation
- Pass file's current collapsed state to determine bulk action direction
- Update tooltip to indicate Alt+Click functionality
- Update tests to include onToggleAllCollapsed prop
This provides a quick way to manage multiple files at once while keeping
the interface clean and discoverable through tooltips.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Add a toggle button in the Files Changed header to collapse or expand
all folders at once. The button shows ChevronsUpDown icon when all
folders are expanded, and ChevronsDownUp when collapsed.
Tasks completed:
- Located Files Changed component
- Implemented collapse/expand icon button functionality
- Verified behavior in local environment
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of exiting when getGitRoot() fails, fall back to undefined
which allows GitDiffParser and other components to use process.cwd()
as the default value. This maintains backward compatibility and allows
difit to work in non-git directories as before.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix the issue where getBlobContent was not considering repoPath when
reading working directory files. This caused incorrect file path
resolution when launching difit from a repository subfolder.
Changes:
- Add repoPath field to GitDiffParser class
- Update getBlobContent to resolve paths against repoPath
- Update test expectations to match new behavior
This addresses the critical issue identified in PR #153 review where
git operations would run from repo root but file reads would happen
from the current working directory.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix working diff isolation bugs issue
* fix the bug of not recog @ @^ commit hash
* Revert progress bar color changes and fix test infrastructure
1. Reverted the third bug fix (progress bar color changes) as requested:
- Changed progress bar from showing completion to showing remaining files
- Moved bar position from left to right
- Updated color logic to match main branch behavior
2. Fixed StorageService.test.ts localStorage mock:
- Implemented proper Storage interface with LocalStorageMock class
- Fixed Object.keys(localStorage) issue by adding _keys helper
- Added proper TypeScript types for test assertions
- All 444 tests now pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix issues
* fix issue for unused func/testing and revert style change
* Update index.test.ts
revert change
---------
Co-authored-by: garrick.zhang <garrick.zhang@bytedance.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>