Items marked ✅ are covered by automated UI integration tests (tests/ui_integration/).
- ✅ No arguments: launches Welcome window
- ✅ Two file paths: opens file diff window
- ✅ Two directory paths: opens directory comparison window
- ✅ Three file paths: opens 3-way merge window
- ✅ One directory (git repo): opens VCS window
- ✅ One directory (not a git repo): prints error and exits
- ✅ Single file path: prints error and exits
- ✅ Non-existent path: prints error and exits
- ✅ File + directory mix: prints error
- ✅ File + patch file: opens patch viewing window
- ✅ Directory + patch file: opens multi-file patch dir comparison
- ✅
-L/--labelflags: custom pane labels shown for 2-file and 3-file modes - ✅
--version/--help: correct output
- "Compare Files" button: two file dialogs, then opens diff; welcome closes
- "Compare Directories" button: two folder dialogs, then opens dir window; welcome closes
- "3-way Merge" button: three file dialogs, then opens merge; welcome closes
- Preferences button / Ctrl+,: opens Preferences dialog
- Ctrl+W: closes welcome window
- Ctrl+N: opens a new welcome window
- Cancel any file dialog: nothing happens
- Chunk backgrounds: blue for replace, green for insert/delete
- Current chunk: bold dark borders when navigating
- Inline word-level highlighting: yellow (changed), pink (deleted), green (inserted)
- Filler lines: thin colored lines where the other side has more content
- Gutter: curved connecting bands between panes
- Chunk map strips: minimap of changes at far edges; clickable to jump
- Chunk map viewport indicator: visible rectangle showing current scroll position
- Syntax highlighting: detected from filename
- Line numbers: shown/hidden per settings
- ✅ Undo (Ctrl+Z) / Redo (Ctrl+Shift+Z): acts on last-focused pane
- Previous change (Alt+Up / Ctrl+E): navigates to previous diff chunk
- Next change (Alt+Down / Ctrl+D): navigates to next diff chunk
- ✅ Chunk label: shows "N changes" or "Change X of N"
- ✅ Go to line (Ctrl+L): entry appears; type number + Enter to jump; Escape to dismiss; focus returns to active pane
- ✅ Blanks toggle: re-diffs ignoring blank lines
- ✅ Spaces toggle: re-diffs ignoring whitespace
- Apply all (⏪ button): copies all non-conflicting chunks right → left
- Apply all: single undo operation (one Ctrl+Z reverts everything)
- Apply all: skips chunks containing conflict markers (
<<<<<<< original/>>>>>>> patch) - Apply all on binary files: button is disabled
- Export patch (Ctrl+Shift+P): save dialog; generates unified diff
- ✅ Swap panes: swaps content, labels, save paths, dirty state; title updates
- ✅ Preferences (Ctrl+,): opens Preferences
- Click right arrow: copies left chunk to right
- Click left arrow: copies right chunk to left
- Right-click gutter: context menu with both copy options
- Paste highlight flash: 500ms blue flash on inserted text
- Horizontal scroll synced between panes
- Vertical scroll synced by chunk between panes
- ✅ Ctrl+F: opens find bar; Ctrl+H: opens find + replace
- Typing highlights all matches in both buffers; shows match count
- Find next (F3 / Enter) / Find prev (Shift+F3): wraps around
- F3/Shift+F3 works from find entry, replace entry, and source pane
- ✅ Escape closes find bar from source pane (not just from find entry)
- Replace: replaces current selection if it matches
- Replace All: replaces in both buffers
- Close (X / Escape): hides bar, clears highlights, returns focus to pane
- After closing find bar, Ctrl+F / Ctrl+H work immediately
- ✅ Ctrl+S: saves focused pane; button goes insensitive
- ✅ Save button insensitive until buffer changes
- ✅ Save writes correct file; button goes insensitive; error dialog on failure
- Save path tracks swaps: after swap, save writes to correct original file
- Save after swap: pane contents and labels remain consistent (no un-swap)
- Save after swap in dir file tab: watcher reload preserves swapped state
- Ctrl+Shift+S (Save As): file dialog; writes to chosen path; updates pane label and tooltip
- Save As updates tab-tracked path (close/unsaved dialog shows new name)
- Ctrl+Shift+L (Save All): saves all dirty panes in current view
- Ctrl+Shift+O: opens focused file in system default app
- ✅ Ctrl+R / F5 (Refresh): reloads both files from disk
- Refresh with unsaved changes: confirm dialog before reloading
- Refresh on blank comparison pane: no-op (doesn't crash)
- External file change: auto-reloads if no unsaved changes
- External change while unsaved: reload skipped
- Save suppresses reload for 600ms (no reload loop)
- Ctrl+W: closes window
- Close with no unsaved changes: closes immediately
- Close with unsaved changes: dialog with per-file checkboxes, Save/Close without Saving/Cancel
- Save failure in dialog: window does NOT close
- Overwrite confirm dialog: Escape key closes it
- Ctrl+Shift+P: export patch dialog opens (sourceview doesn't consume it)
- Alt+Right on a diff chunk: copies left chunk content to right (no file copy dialog)
- Alt+Left on a diff chunk: copies right chunk content to left (no file copy dialog)
- Alt+Left/Right with no current chunk: nothing happens
- In file diff tab opened from dir window: Alt+Left/Right copies chunks, does NOT trigger dir file copy
- Alt+Shift+Right: pulls content from right pane into focused pane
- Alt+Shift+Left: pulls content from left pane into focused pane
- Pull when focused on the same side as pull direction: no-op
- ✅ Alt+Delete: deletes the current chunk's text from the focused pane
- Alt+Delete with no current chunk: nothing happens
- Alt+Delete at end of buffer: removes preceding newline too
- ✅ Alt+PageDown: switches focus to next pane (wraps around)
- ✅ Alt+PageUp: switches focus to previous pane (wraps around)
- In 3-way merge: cycles left→middle→right→left
- ✅ F11: toggles fullscreen mode
- ✅ F11 again: exits fullscreen
- ✅ Identical files: "Files are identical" info bar; edit one side -> bar disappears; undo -> reappears
- ✅ Binary files: info bar, panes read-only, patch button disabled, swap/nav/blanks/spaces/undo/redo buttons disabled
- Empty files: no chunks, label shows "No changes"
- ✅ Empty file vs non-empty file: all-insert chunks on one side
- Very large files (10K+ lines): background diff, UI stays responsive
- UTF-8 with multi-byte characters: inline highlighting correct
- ✅ Files with only whitespace differences + Spaces toggle on: no differences shown
- ✅ Files with only blank line differences + Blanks toggle on: no differences shown
- ✅ Swap panes twice: returns to original state
- ✅ Alt+Up/Down: cursor moves to chunk start line
- Click in pane, then Alt+Down: navigates from cursor position, not from top
- Navigate chunks with cursor in different positions: cursor-aware seeking
- Alt+Up/Down intercepted: sourceview move-lines action does not fire
- Wrap-around navigation: Alt+Down past last chunk wraps to first (if enabled in prefs)
- ✅ Wrap-around off: Alt+Down on last chunk stays; Alt+Up on first chunk stays
- ✅ Tree view with expandable directories
- Columns: Name (with icon), Size, Modification time
- Directory headers above each pane with full path as tooltip
- Copy path button next to each header: copies full absolute path to clipboard
- Copy path after swap: copies the swapped (current) path, not the original
- Status colors: blue (different), orange (left-only), green (right-only), red (conflict), dim italic (missing side)
- ✅ Double-click file row: opens diff in new notebook tab
- Enter on file row: opens diff in new notebook tab
- ✅ Enter on directory row: expands/collapses folder
- Enter after closing a file tab: still works (no broken activation state)
- Left/Right arrow keys: switch focus between panes
- Selection syncs between panes (highlighted on both sides)
- Active pane: accent-color border; inactive pane: dim border + reduced opacity
- Switching panes: no scroll jump, keyboard cursor syncs to selected row
- Copy to left (Alt+Left): copies right to left; confirms if overwriting
- Copy to right (Alt+Right): copies left to right; confirms if overwriting
- Delete (Delete key): trashes selected file with confirmation
- ✅ Collapse all: collapses every expanded directory row
- ✅ Expand all: expands every directory row
- Apply all non-conflicting (⏪ button): copies all non-conflicting files right → left
- Apply all: confirmation dialog shows file count
- Apply all: handles modified, new, and deleted files
- Apply all: skips conflicting files
- Swap panes: rescans, updates headers and window title
- Preferences (Ctrl+,)
- Right-click selects the clicked row first
- Right-click targets correct row: select row A, right-click row B, action operates on B (not A)
- "Open Diff": opens diff tab (disabled for directories)
- "Copy to Left" / "Copy to Right": enabled for appropriate statuses
- "Delete": enabled only when file exists on focused side
- "Open Externally" / "Copy File Path": enabled only when file exists on focused side
- Missing-side file: Copy, Delete, Open Externally, Copy Path all disabled
- ✅ Directory tab labeled with "dir1 — dir2"
- File tabs labeled with dir-relative names
- Opening file diff tab: left pane has focus, cursor at line 1
- Tab close (X): prompts for unsaved changes
- Ctrl+W on Directory tab: closes entire window
- ✅ Ctrl+W on file tab: closes that tab
- ✅ Same file not opened twice: switches to existing tab
- ✅ Alt+1..9: switches to tab by index
- Ctrl+N: opens New Comparison tab
- New Comparison tab: "Compare Files" and "3-Way Merge" buttons with file choosers
- New Comparison → Compare Files: opens diff tab, removes new-comparison tab
- New Comparison → 3-Way Merge: opens merge tab, removes new-comparison tab
- Blank comparison (Ctrl+T or from New Comparison): opens editable blank diff tab
- Directory rescans on FS changes (500ms poll)
- Expanded directories and selection restored after rescan
- Open tab buffers reloaded unless unsaved
- Filtered names (.git, node_modules, etc.) excluded from tree
- Custom filters from preferences applied
- Hide hidden files on (default): dotfiles excluded from tree
- Hide hidden files off: dotfiles shown in tree
- Collapse all with missing-side files expanded: no crash
- Deeply nested directory trees
- Copy directory recursively: all nested files copied
- Dir -> file tab -> swap -> edit -> save dialog shows correct filename
- Window close with unsaved tabs: dialog lists all unsaved files
- ✅ Three panes: left (read-only), middle (editable), right (read-only)
- ✅ Left/right save buttons hidden; only middle has save button
- Left gutter: bands connecting left to middle
- Right gutter: bands connecting middle to right
- Conflict backgrounds: red/pink overlays on middle where left/right overlap
- Chunk maps on both edges
- ✅ Undo/Redo (Ctrl+Z / Ctrl+Shift+Z): acts on active pane
- ✅ Previous/Next change (Alt+Up/Down): per-pane cursor-based navigation
- Navigation only places cursor on involved panes (not the third uninvolved pane)
- Previous/Next conflict (Ctrl+J / Ctrl+K): navigates
<<<<<<<markers in middle - Conflict label: "N conflicts" / "Conflict X of N" / "No conflicts"
- Go to line (Ctrl+L)
- ✅ Blanks / Spaces toggles
- Preferences (Ctrl+,)
- Only inward arrows: left gutter has right-arrows only, right gutter has left-arrows only
- Click left gutter arrow: copies left chunk into middle
- Click right gutter arrow: copies right chunk into middle
- Right-click context menus: "Copy Left -> Middle" / "Copy Right -> Middle"
- Horizontal sync across all three panes
- Vertical scroll synced by chunk across all three panes
- Ctrl+F/H: find bar highlights matches across all three buffers
- Match count sums all three buffers
- Replace All replaces only in editable middle buffer (not read-only left/right)
- ✅ Ctrl+S: saves middle pane
- Ctrl+Shift+S (Save As): saves middle to a new path; updates pane label
- Save As updates tab-tracked path (close/unsaved dialog shows new name)
- Ctrl+Shift+L (Save All): saves middle if dirty
- Ctrl+Shift+O: opens focused file externally (works for all 3 panes)
- Ctrl+R / F5 (Refresh): reloads all 3 files from disk
- Refresh with unsaved middle: confirm dialog before reloading
- After Save As, watcher reloads from new path (left/right still watch originals)
- Binary files: all panes show info bar; all read-only; middle save hidden
- All three identical: no changes, no chunks
- Both sides modify same region: conflict overlay on middle
- Copy left then copy right to same region: middle updates; undo works
- Undo/redo across panes: applies to active pane's buffer
- ✅ Window title: "mergers — reponame (branchname)"
- ✅ Branch name shown in toolbar
- ColumnView: Status, File, and Extra columns
- Status colors: Modified/Renamed = blue, Added/Untracked = green, Deleted = orange
- ✅ Extra column: "Staged" for fully staged, "Partially staged" for staged+unstaged
- ✅ Conflict files: Extra column empty (not falsely "Staged")
- ✅ Repo path label and changed file count
- ✅ Double-click / Enter: opens diff tab (HEAD vs working copy)
- Modified/Renamed: HEAD content on left, working file on right
- Added/Untracked: empty left, working file on right
- Deleted: HEAD content on left, empty right
- Already-open tab: switches to existing tab
- Right-click selects clicked row
- "Open Diff": opens diff (disabled for Untracked)
- "Discard Changes": runs
git checkout --with confirmation (disabled for Untracked) - "Stage": runs
git add(disabled when already staged) - "Stage" on conflicted file: confirmation dialog warning it marks conflict resolved
- "Unstage": runs
git restore --staged(enabled only when staged) - "Trash": moves to trash with confirmation (Untracked only)
- ✅ Commit button in toolbar
- Commit dialog shows branch name
- Commit dialog lists staged files (read-only)
- Commit dialog prefills message from
.git/MERGE_MSG - Commit button disabled when no staged files or empty message
- Successful commit refreshes file list
- ✅ Modified toggle (on by default): shows M, A, D, R, C files
- ✅ Untracked toggle (on by default): shows U files
- Ignored toggle (off by default): shows ignored files
- Toggling filters updates file list and count label
- Auto-refresh on FS changes (500ms poll)
-
.gitdirectory events ignored - Smart update: skips rebuild if status unchanged
- ✅ "Changes" tab first; Ctrl+W on it closes window
- Ctrl+W on file tab: closes tab with unsaved check
- Window close: checks all tabs for unsaved changes
- Temp directory cleaned up on window destroy
- Alt+1..9: switches to tab by index
- Ctrl+N: opens New Comparison tab
- Paths with
..components refused
- ✅
mergers file.c patch.diff: opens single-file patch view (original vs patched) - ✅
mergers dir/ patch.diff: opens multi-file patch view (dir comparison) - ✅
.diffextension auto-detected as patch mode - ✅ Ctrl+W closes patch window cleanly
-
-Llabels applied to patch views - Invalid patch file: error dialog shown
- Base path does not exist: error dialog shown
- ✅ Window title contains "patch" and base filename
- ✅ Two text panes shown
- ✅ Left pane: original file content; Right pane: patched result
- ✅ Right pane contains inserted lines from patch
- ✅ Chunk label shows change count
- ✅ Tab label contains "patch"
- Conflict hunks shown with
<<<<<<< original/>>>>>>> patchmarkers in right pane - Labels show base filename and "filename (patched)" (not temp paths)
- Window destroy cleans up temp directory
- ✅ Window opens successfully
- ✅ Shows patched files (e.g. "different.txt")
- ✅ Shows subdirectories from the patch
- Left header shows base directory name (not temp path)
- Left header tooltip shows original base path (not temp path)
- Right header shows "dirname (patched)"
- Right header tooltip shows temp directory path
- Conflicted files colored red in tree view
- Same-status files filtered out of tree view
- Empty directories filtered out of tree view
- Double-click file: opens diff tab with resolved paths
- Diff tab left pane header/tooltip: shows original file path (resolved through symlink)
- Diff tab right pane: shows temp patched file path
- Unsaved changes dialog shows original path (not temp path)
- ⏪ button: confirmation dialog shows file count
- Modified files (Different): copied right → left through symlink, then removed from view
- New files (RightOnly): created in original directory, parent dirs created as needed
- Deleted files (LeftOnly): removed from original directory
- Conflicted files: skipped (not applied)
- View reloads after apply; applied files vanish from tree
- File watcher suppressed during bulk copy (no spurious reloads)
- Errors during apply: error dialog shown with details
- Normal window close: temp dir removed (connect_shutdown)
- SIGINT (Ctrl+C): temp dir removed (ctrlc handler)
- SIGTERM (kill): temp dir removed (ctrlc handler)
- Multiple patch windows: all temp dirs tracked and cleaned up
- Font: chooser dialog; live-apply
- Color scheme: dropdown; live-apply; case-insensitive matching
- Show line numbers: toggle; live-apply
- Highlight current line: toggle; live-apply
- Word wrap (None/Word/Character): dropdown; live-apply
- Tab width (1-16): spin button; live-apply
- Hide hidden files: toggle; default on; saved to settings
- Wrap-around navigation: toggle; saved to settings
- File filters: add/remove entries; saved on dialog close
- Settings persisted to
~/.config/mergers/settings.toml - Changes applied to all views in the parent window
- Escape closes preferences dialog
- Close button at bottom closes preferences dialog
- ✅ Window size saved to
settings.tomlon close - Window maximized state restored on relaunch
- Window fullscreen state restored on relaunch (macOS green button)
- Per-window default sizes: VCS=700×500, merge=1200×600, file diff=900×600, dir=900×600
- Old configs without window fields get sentinel defaults (0 = use per-window default)
- Size only saved when not maximized/fullscreen (avoids saving inflated dimensions)
- Linux/GNOME: auto-detects system dark mode
- macOS: falls back to
defaults read -g AppleInterfaceStyle - Sets
gtk_application_prefer_dark_themefor GTK widgets
| Shortcut | Action |
|---|---|
| Ctrl+Q | Quit (close all windows) |
| Ctrl+W | Close current tab/window |
| Ctrl+, | Preferences |
| Ctrl+Z | Undo |
| Ctrl+Shift+Z | Redo |
| Alt+Up / Ctrl+E | Previous change |
| Alt+Down / Ctrl+D | Next change |
| Ctrl+J | Previous conflict (merge only) |
| Ctrl+K | Next conflict (merge only) |
| Ctrl+F | Find |
| Ctrl+H (Cmd+Shift+H on macOS) | Find + Replace |
| F3 | Find next |
| Shift+F3 | Find previous |
| Ctrl+L | Go to line |
| Alt+PageUp | Previous pane |
| Alt+PageDown | Next pane |
| Alt+Shift+Left | Pull chunk from left |
| Alt+Shift+Right | Pull chunk from right |
| Alt+Delete | Delete current chunk |
| F11 | Toggle fullscreen |
| Ctrl+Shift+P | Export patch (file diff only) |
| Alt+Left | Copy chunk right→left (file diff) / Copy file to left (dir) |
| Alt+Right | Copy chunk left→right (file diff) / Copy file to right (dir) |
| Ctrl+S | Save active pane |
| Ctrl+Shift+S | Save As (file diff / merge) |
| Ctrl+Shift+L | Save All dirty panes |
| Ctrl+Shift+O | Open focused file externally |
| Ctrl+R / F5 | Refresh (reload from disk) |
| Ctrl+N | New Comparison tab (dir/VCS windows) |
| Alt+1..9 | Switch to tab by index (notebook windows) |
| Delete | Delete selected (dir only) |
| Enter | Open selected file / expand-collapse dir (dir/VCS) |
| Escape | Close find bar / go-to-line / preferences dialog |
- "Keyboard Shortcuts" button in Preferences: opens shortcuts dialog
- Dialog lists all shortcuts grouped by category