Skip to content

Tags: joske/mergers

Tags

v0.8.2

Toggle v0.8.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Add patch file support (unified & context diffs) (#46)

* Add unified diff parser with tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add context diff parser with tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add patch hunk application with tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add patch file auto-detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add CompareMode::Patch variant and CLI detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Implement patch window for single-file and multi-file patches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add round-trip parse+apply integration tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Multi-file patches use dir comparison with symlinked originals

- Multi-file patches now open as a directory comparison showing only
  files mentioned in the patch, not the entire base tree
- Left side uses symlinks to originals so saves write through to disk
- Added PatchKind (Modified/Added/Deleted) to handle /dev/null paths
- Fixed out-of-bounds panic when patch references lines beyond file end
- Added Kompare to README comparison table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add apply-all, fix temp paths, conflict coloring, and cleanup

- Apply-all button (⏪ icon) for file diffs and dir patch view
  - File diff: single undo operation, skips conflict-marker chunks
  - Dir view: handles modified, new (RightOnly), and deleted (LeftOnly) files
- Fix dir view showing temp paths in headers/tooltips for patch mode
  - Labels control display names, separate tooltip_dirs for hover text
  - Resolve symlinks in open_file_diff so diff headers show original paths
- Wire FileStatus::Conflict to color conflicted files red in dir view
- Clean up patch temp dirs on shutdown and SIGTERM/SIGINT (ctrlc crate)
- Filter Same entries and empty dirs from patch dir view
- Update README with patch usage examples and comparison table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix security and robustness issues in patch support

- Sanitize patch file paths: reject `..` traversal and absolute paths
- Use unpredictable temp dir name and create_dir (not create_dir_all)
  to prevent symlink race attacks
- Read only 50 lines via BufReader for patch detection instead of
  entire file
- Remove unused --patch-format CLI flag
- Propagate temp dir creation errors instead of silently swallowing
- Add /dev/null detection and a/b prefix stripping to context diff
  parser for correct Added/Deleted classification
- Preserve trailing newlines in apply_hunks and apply_hunks_best_effort

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use line-start marker detection for apply-all conflict skip

Check for lines starting with <<<<<<< and >>>>>>> instead of exact
string matches like "<<<<<<< original". Avoids false positives on
files that happen to contain those exact substrings mid-line.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Apply fmt and clippy pedantic fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix Windows path traversal, single-file patch mismatch, and missing base handling

- sanitize_patch_path: reject backslash traversal (..\) and Windows
  drive letters (C:) in addition to Unix absolute paths
- Single-file patch mode: match patch entry by filename instead of
  blindly using first entry; error if multi-file patch has no match
- Missing base files in directory patch mode now write error to both
  left and right sides (shows as Modified/conflict, not as RightOnly
  which misleadingly looks like an added file)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Reject ambiguous single-file patch matches and surface missing deleted bases

- Single-file mode: reject multi-file patches where multiple entries
  share the same basename (e.g. src/foo/config.rs vs tests/config.rs)
  instead of silently picking the first match
- Deleted-file entries with missing base now write error to both sides
  and mark as conflict, so they appear in the UI instead of vanishing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add 26 edge case tests for patch parsing, application, and conflict detection

Cover: path sanitization (mixed separators, hidden files, bare ..),
apply_hunks error paths (overlapping, past EOF, context mismatch),
delete-everything and empty-hunks-list cases, best-effort all-fail
and trailing newline on conflict, unified diff git prefixes and
no-newline-at-EOF marker, added/deleted file detection, context diff
multi-hunk, is_patch_file boundary at line 50, find_conflict_blocks
unmatched/nested markers, and conflict_at_cursor_fast boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump

* bump to 0.8.2

* bump clap

* Fix bench CI: replace deprecated criterion::black_box with std::hint

Also fix clippy format_push_string warnings in patch tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add 13 UI integration tests for patch viewing feature

Single-file patch (8 tests): window title, pane content, chunk label,
tab label. Multi-file patch (3 tests): window opens, shows patched
files and subdirectories. Detection (2 tests): .diff extension
triggers patch mode, Ctrl+W closes cleanly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add patch viewing and apply-all test cases to TESTING.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Mark 13 automated patch UI tests in TESTING.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review: apply-all targets, dedup, docs, cleanup

- RightOnly apply now copies to real base dir (not temp symlink dir)
- LeftOnly delete resolves symlink to remove original file
- sanitize_patch_path docs updated to match strip behavior
- is_right_a_patch reuses patch::is_patch_file instead of duplicating
- cleanup_patch_temp_dirs clears vec after removal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review round 2: gate removals, permissions, error dialog

- Apply-all file removal gated to patch mode only (non-patch keeps files)
- LeftOnly delete gated to patch mode only
- Temp dir created with 0o700 permissions on Unix
- show_patch_error uses active window when available, closes temp window
- PR description updated: sanitize_patch_path strips (not rejects) absolute paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review round 3: symlink recursion, range validation, tests

- read_dir_entries uses entry.file_type() for is_dir (avoids following
  symlinks to dirs, preventing infinite recursion with cyclic links)
- parse_context_range validates end >= start to prevent underflow
- Add 6 unit tests for collect_actionable_files status filtering logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review round 4: Added fallback, LeftOnly filter, registry cleanup

- PatchKind::Added falls back to apply_hunks_best_effort on error
  (shows conflict markers instead of empty content)
- LeftOnly excluded from actionable set in non-patch mode (was a no-op)
- Window destroy removes entry from PATCH_TEMP_DIRS registry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review round 5: registry timing, dir cleanup, conflicts, parse_patch

- Register PATCH_TEMP_DIRS only after successful window creation (not on
  early error paths)
- Multi-file patch window cleans up temp dir on destroy via on_destroy callback
- Conflict coloring applies to LeftOnly/RightOnly entries in .mergers-conflicts
- parse_patch returns error when no file headers found (not Ok(vec![]))

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review round 6: tempfile, /dev/null detection, hunk validation

- Use tempfile crate for secure temp dir creation (0o700, collision-resistant)
- Check conflicts before content comparison so identical-content conflicts
  still show as Conflict
- Use stripped paths (not raw headers) for /dev/null detection in both
  unified and context diff parsers
- Validate hunk old_count/new_count against actual line counts in apply_hunks
- parse_patch returns error when no file headers found

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Copilot review round 7: swap safety, tooltip swap, write errors

- Disable apply-all button when panes are swapped in patch mode
- Swap tooltip overrides alongside dirs/labels in swap handler
- Log and mark as conflict when fs::write fails for patched files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Replace placeholder collect_actionable_files tests with logic tests

The GTK ListStore walker can't be unit tested without main-thread
init. Replace stub matches!() tests with focused predicate tests
covering: status filtering (Different/RightOnly/LeftOnly included,
Conflict/Same excluded), mixed-status filtering, and non-patch mode
LeftOnly exclusion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix trailing newline on empty output, normalize backslashes, unscope marker filter

- sanitize_patch_path: normalize backslashes to forward slashes so
  Windows-produced patches create correct dir structure on Unix;
  return String instead of &str
- apply_hunks/apply_hunks_best_effort: don't append trailing newline
  when output is empty (delete-everything case)
- read_dir_entries: remove hardcoded .mergers-conflicts skip that hid
  real user files with that name in non-patch mode
- tempfile::TempDir::keep() returns PathBuf in tempfile 3.26 — no fix
  needed (copilot comment was incorrect)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Tighten context diff detection, fix conflict skip, symlink-to-dir safety

- is_context_diff/is_patch_file: require both separator and file header
  pair to avoid mis-routing unified diffs containing "***************"
- parse_patch: return error when context parsing yields no file entries
- apply-all: skip chunks containing any conflict marker line, not just
  paired markers, to handle conflict blocks split across chunks
- Bulk apply: re-mark directories as saving per file to extend the
  600ms watcher suppression window across the entire operation
- read_dir_entries: use symlink_metadata for size/mtime; treat
  fs::read errors as Different instead of Same (fixes symlink-to-dir
  misclassification)
- Remove unused Python test imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove empty-line hunk special case, scope conflict marker to patch mode, make right pane read-only

- Unified diff parser: remove is_empty() special case that treated
  blank lines as context, which could misparse patches with blank
  separator lines between hunks
- scan_tree: only read .mergers-conflicts marker file in patch mode
  to avoid misclassifying files in normal directory comparisons
- Single-file patch: set right text view to non-editable since it
  shows generated patched output
- Add right_text_view to DiffViewResult for patch mode access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fmt

* bump

* bump

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.8.1

Toggle v0.8.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Performance optimizations and correctness fixes (#42)

* Fix stale conflict cache and zero-width conflict regions

Eagerly invalidate conflict marker/block caches in connect_changed
so cursor handler never reads stale data while async re-diff is pending.

Filter zero-width regions in middle_conflict_regions to match the
middle-pane editor which already skips tag_start == tag_end.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Avoid clone on conflict cache hit, binary search conflict regions

- get_markers/get_blocks return Rc<Vec<>> instead of cloning the Vec
  on every cache access
- Binary search (partition_point) for conflict region overlap in
  merged_gutter_chunks instead of linear scan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add unit tests for conflict region and merged gutter logic

Tests cover middle_conflict_regions (no overlap, basic overlap,
zero-width filtering, adjacent merging, equal-chunk skipping) and
merged_gutter_chunks (no conflicts, conflict marking with binary search).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Eliminate redundant stat syscall in directory scanning

Derive is_dir from the metadata already fetched by DirEntry::metadata()
instead of calling Path::is_dir() which does a second stat syscall.
Also enable debug symbols in release profile for flamegraph profiling.

Flamegraph confirmed: read_dir_entries dropped from 26% to 11.5% of
runtime, and total directory comparison time reduced by ~22%.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.8.1

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.8.0

Toggle v0.8.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Performance optimizations and benchmarks (#41)

* Add criterion benchmarks at large-project scale

Expose internal pure functions via #[doc(hidden)] _bench module for
benchmarking. Inputs sized for real monorepo workloads: 500k-line
diffs, 50k chunks (O(n²) conflict detection), 100k navigation
chunks, 50k VCS entries, 500k-line conflict marker scans.

Baseline results show O(n²) conflict functions taking 0.4-1.2s per
call at 30-50k chunks — these run every frame during scroll.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Optimize O(n²) conflict detection and navigation to O(n+m)

- conflict_flags: two-pointer sweep instead of nested loops (3163x at 50k)
- middle_conflict_regions: two-pointer merge (4517x at 50k)
- find_next_chunk: iterator-only, no Vec allocation (8.6x at 100k)
- format_chunk_label: single-pass counting, no Vec (1.3x at 100k)
- myers: push prefix before reverse instead of insert(0)

The three O(n²) functions that ran every frame during scroll now
complete in <260µs at 50k chunks, down from 0.4-1.2 seconds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Cache conflict flags, binary-search conflict_at_cursor, O(n) tree restore

- chunk_map: pass pre-computed conflict flags instead of recomputing
  every frame in the draw callback
- conflict_at_cursor: add find_conflict_blocks + conflict_at_cursor_fast
  with O(log n) binary search (12ns vs 18ms at 490k lines)
- dir_window: HashSet + single forward scan for expanded-state
  restoration instead of O(n*m) nested loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fmt

* clippy

* add bench to CI

* Add conflict_flags proptest and cache conflict scans in merge view

- Add 5 unit tests + 2 proptests verifying conflict_flags two-pointer
  matches naive O(n*m) reference implementation
- Cache find_conflict_markers/find_conflict_blocks in merge view cursor
  handler to avoid 3 redundant O(n) buffer scans per cursor move

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add benchmark layer to README testing section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.8.0

* Use criterion-compare-action for benchmark regression detection

Replaces bare `cargo bench` with boa-dev/criterion-compare-action
which benchmarks both the base branch and PR, then posts a comparison
comment showing regressions/improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix bench CI: specify benchName to avoid running lib harness

The `--save-baseline` flag is criterion-specific and fails when passed
to the built-in lib bench harness. Specifying benchName=benchmarks
targets only the criterion bench binary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix bench CI: handle base branch without benchmarks

criterion-compare-action fails when the base branch doesn't have the
bench target. Replace with manual script that gracefully skips
comparison when base has no benchmarks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.7.4

Toggle v0.7.4's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
vcs (#40)

* Fix button role name portability in UI tests

AT-SPI reports "button" on some platforms and "push button" on others.
Add is_button() helper to conftest and use it in all test files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add VCS backend: current_branch, staged_files, commit, commit_message_prefill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Show current branch name in VCS window title and toolbar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add status filter toggles (Modified/Untracked/Ignored) to VCS view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add commit dialog to VCS view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add UI tests for VCS branch display, filters, and commit button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update TESTING.md with VCS commit dialog and filter toggles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.7.4

* Fix current_branch test for detached HEAD in CI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix VCS UI tests: toggle button role, title with branch name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.7.3

Toggle v0.7.3's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Remember window size and state across sessions (#39)

* Remember window size and maximized state across sessions

Saves width, height, and maximized flag to settings.toml on window
close. Restores on next launch. Size is only saved when not
maximized/fullscreen (like Meld). Closes #9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Also remember fullscreen state across sessions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add unit and UI tests for window state persistence

Unit tests: defaults, roundtrip, backward compat with old config.
UI test: verifies settings.toml contains window dimensions after close.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix clippy field_reassign_with_default in test

Use struct init syntax instead of default + reassign to satisfy
clippy -D warnings on CI (Rust 1.94).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address PR review: save-on-close ordering, sentinel defaults, hermetic test

- Only persist window state when close actually proceeds (not cancelled)
- Use 0 as sentinel for window_width/height so per-window defaults apply
  until user has actually resized (VCS=700x500, merge=1200x600, etc.)
- UI test uses tmp_path for XDG_CONFIG_HOME instead of real user config
- Assert process exited before checking settings file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.7.3

* Add kill fallback for SIGTERM timeout in window state test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add window state persistence section to TESTING.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.7.2

Toggle v0.7.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Add pane switching, pull/delete chunk, fullscreen, and focus border (#38

)

* Add missing merge save UI test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add design doc for pane switching, pull/delete change, fullscreen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add implementation plan for pane switch, pull/delete change, fullscreen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add key mappings and pane switching for Alt+PageUp/Down

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add pull chunk (Alt+Shift+Left/Right) and delete chunk (Alt+Delete) actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add F11 fullscreen toggle and register new accelerators

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add UI tests for pane switching, delete chunk, and fullscreen features

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* remove

* ignore the plan files

* Add focused-pane border highlight to diff and merge views

Reuse the directory view's accent-color border for the active pane.
Renamed CSS classes from dir-pane-* to pane-* for shared use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Mark automated test coverage in TESTING.md and ignore docs/plans/

54 checklist items now marked ✅ as covered by UI integration tests.
Added docs/plans/ to .gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.7.2

* Fix Alt+Shift fallthrough and guard delete_chunk against empty ranges

Alt+Shift+keys other than Left/Right now fall through to normal Alt
mappings instead of returning None. delete_chunk early-returns when
start >= end to prevent unintended deletions on empty chunk sides.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add unit tests for map_key_to_action key binding dispatch

Covers Alt+Shift fallthrough regression: Alt+Shift+Up/Down must still
map to prev/next-chunk rather than returning None.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address PR review: deduplicate test helpers, strengthen assertions

- Move copy_fixture and focus_and_type to conftest.py (DRY)
- Assert "MERGE_EDIT" content in merge save test (not just file changed)
- Verify focused pane index changes after Alt+PageUp/Down (not just window survives)
- Use copy_fixture from conftest in delete chunk test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Guard focus_and_type against missing windows, fix TESTING.md markers

- focus_and_type now raises if no X11 windows found for pid
- Align [x] markers with ✅ convention for items with UI tests
- Uncheck pull chunk items that lack automated tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove unused import and dead variable in test_merge_save

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.7.1

Toggle v0.7.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Refactor: extract shared helpers, deduplicate views (#35)

* Quick-win refactors: extract helpers, fix grammar, use Side enum

- Extract draw_gutter_band helper to deduplicate cairo band drawing
- Extract is_binary helper in file I/O code
- Extract find_window helper replacing inline root/downcast pattern
- Extract git_show helper to deduplicate head_content/stage_content
- Replace is_left:bool with Side enum in chunk_map/diff_state
- Fix "1 changes" grammar bug → "1 change"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Wire nav buttons to GActions, fix merge toolbar label jitter

- Replace duplicated button connect_clicked handlers with thin
  wrappers that delegate to the corresponding GActions (-272 lines)
- Add grab_focus() to action handlers so keyboard focus follows
  navigation (clone-first pattern to avoid RefCell double-borrow)
- Set fixed width_chars on chunk/conflict labels in merge toolbar
  to prevent layout shift when label text changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Extract shared key bindings, chunk map helper, fix viewport indicator

- Extract map_key_to_action + KeyBindings struct to deduplicate ~120
  lines of key handler logic between diff_view and merge_view
- Extract create_chunk_map helper to deduplicate chunk map setup
- Simplify scroll redraw wiring with loops
- Fix chunk map viewport indicator using widget foreground color
  instead of editor scheme to detect dark/light GTK theme

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Deduplicate dir_window focus handlers and copy actions

- Parameterize focus handlers with (own_view, other_view, is_left) loop
- Parameterize copy actions with (name, src_dir, dst_dir, status) loop
- Replace inline root/downcast patterns with find_window helper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Split build_new_comparison_tab, extract tab/scroll helpers

- Extract make_closeable_tab_label and display_name helpers
- Extract open_merge_comparison_tab (merge_view.rs) and
  open_dir_comparison_tab (dir_window.rs) paralleling open_file_diff_paths
- Simplify build_new_comparison_tab from 332 to ~160 lines
- Move setup_scroll_sync_3way from merge_view.rs to scroll_sync.rs
- Extract sync_hscrolls helper, deduplicating horizontal sync wiring

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add context menu row targeting test case to TESTING.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix clippy warnings, formatting, and missing blank-path guard

- Move DIFF_KEYS and MERGE_KEYS statics to module level (clippy
  items_after_statements)
- Add is_blank_path guard in merge view open-externally action
- Run cargo +nightly fmt on files committed without formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix reference cycles in scroll sync and page-removed handler

- sync_hscrolls: capture Adjustments instead of full ScrolledWindows to
  avoid holding strong refs to pane widgets in signal closures
- open_dir_comparison_tab: use weak ref for widget in page-removed
  handler and disconnect the signal after it fires to prevent handler
  accumulation and widget leaks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add dbus-run-session to test-release and document required UI test packages

Wrap pytest in dbus-run-session so AT-SPI works in containers.
List all system packages needed to run the UI integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix collapse-all segfault and disable context menu on missing-side files

- Collapse-all iterated with a pre-computed bound; collapsing rows
  removed children and shrank the model, causing out-of-bounds access.
  Fixed by iterating in reverse.
- Context menu actions (Delete, Copy, Open Externally, Copy Path) were
  available on the pane where a file doesn't exist, leading to wrong-side
  operations. Now all per-file actions are disabled on the missing side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add collapse/expand all toolbar buttons, canonicalize dir header tooltips

- Add linked collapse-all / expand-all button pair to directory toolbar
- Canonicalize directory paths so pane header tooltips show full absolute paths
- Update TESTING.md with collapse/expand, missing-side context menu test cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add copy-path buttons to directory pane headers

Frameless edit-copy-symbolic button next to each header label copies the
full absolute directory path to the clipboard.  Path tracks swaps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Left-align modification time columns in directory comparison

Bold text on "changed" rows made right-aligned dates shift relative to
non-bold rows.  Switch date columns to left-alignment (sizes stay
right-aligned) and widen from 180px to 200px.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Make directory comparison columns resizable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Show conflict chunks in red on merge view chunk map strips

Pass conflict flags through to chunk map drawing so chunks that overlap
between left and right sides render with band_conflict() color instead
of the regular blue/green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix Replace All modifying read-only buffers in merge view

Replace All now skips non-editable TextViews, so in 3-way merge it only
replaces in the middle (editable) buffer.  Updated TESTING.md to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Return focus to active pane when closing find bar

Clone the TextView out of the RefCell before calling grab_focus() to
avoid a borrow conflict — grab_focus() triggers the focus-enter handler
which needs borrow_mut() on the same RefCell.

Also fix Replace All modifying read-only buffers in merge view: now
skips non-editable TextViews.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add Extra column, Unstage action, and stage-conflict warning to VCS view

- Parse staged/partially-staged state from porcelain XY bytes
- Skip staged detection for conflict entries (XY is conflict type, not
  index/worktree status)
- Add "Extra" column showing Staged/Partially staged
- Add "Unstage" context menu action (git restore --staged)
- Stage on conflicted file shows confirmation dialog
- Enable/disable Stage vs Unstage based on file state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump

* fmt

* bump to 0.7.1

* Fix clippy warnings and Copilot review comments

- Add #[must_use] to unstage_file
- Add #[allow(too_many_arguments)] to draw_chunk_map
- Add -- argument terminator in stage_file and unstage_file
- Distinguish fully staged from partially staged for Stage action

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.7.0

Toggle v0.7.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
refactoring + bugfixes (#34)

* Extract shared helpers from diff/merge views and consolidate file watchers

Move duplicated code into common.rs:
- build_find_bar(): find/replace bar + goto-line + 5 GActions (~220 lines x2)
- build_nav_button_group(): prev/next navigation buttons
- build_undo_redo_box(): undo/redo buttons wired to active view
- build_filter_toggles(): blank-line and whitespace toggle buttons
- start_file_watcher(): generic file watcher with on_tick callback
- build_app_window(): shared window builder with notebook and actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Split common.rs (4,419 lines) into 12 focused submodules

Convert the monolithic common.rs into a common/ directory module with
dedicated files for each responsibility area: editor, gutter, helpers,
tabs, find_bar, scroll_sync, navigation, toolbar, search, chunk_map,
file_watcher, and tests. Pure code motion with no logic changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Refactor FileTab into Diff/Merge enum with PaneInfo

Replace flat FileTab struct with a Diff/Merge enum. Introduce PaneInfo
to hold per-pane (path, buffer, save button) state. Accessor methods
(saveable_panes, is_reloadable) eliminate dummy save buttons and
empty-path guards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Extract shared editor action helpers (refresh, save-as, save-all)

Move duplicated refresh/save-as/save-all logic from diff_view and
merge_view into shared helpers in common/helpers.rs. Each view still
wires its own actions but calls the shared functions for the actual
save/reload/dialog logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Deduplicate dir_window button handlers via action group

Replace ~140 lines of duplicated copy-left/copy-right/delete button
click handlers with one-liner activate_action calls. The action group
definitions (already used for keyboard shortcuts) now serve both
buttons and keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix merge Save As stale tab path and remove duplicate action group insert

- Pass middle_tab_path through MergeViewResult so FileTab::Merge shares
  the same Rc as the save-as action, keeping close/unsaved dialogs in
  sync after Save As.
- Remove duplicate vcs_tab.insert_action_group call in vcs_window.rs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix merge tab close prompt showing stale path after Save As

Read the live middle_tab_path Rc instead of capturing a static String
from second_path at construction time. The confirm_unsaved_dialog now
shows the current file name after Save As.

The merge watcher intentionally tracks original source paths (not the
Save As target) since the watcher monitors source files being merged,
matching Meld's behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update TESTING.md with missing test cases

Add 42 test cases for features not previously covered: Save As, Save
All, Open Externally, Refresh, tab navigation (Ctrl+1..9), New
Comparison (Ctrl+N), blank comparison, wrap-around navigation, chunk
map viewport indicator, and keyboard shortcuts dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix error message for non-existent single path argument

`path.is_dir()` returns false for non-existent paths, causing a
misleading "single file argument not supported" error. Add an explicit
existence check before the is_dir/is_file branches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Support -L/--label flags for directory comparison

Wire labels through build_dir_window → build_dir_tab to override
pane header text. Labels swap correctly with the swap-panes button.
Tooltips still show the actual directory path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix CLI help: show <LABEL> instead of <LABELS> for -L flag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Repaint chunk borders when clicking inside a chunk

Cursor tracking updated current_chunk index but never triggered a
redraw, so bold borders only appeared after keyboard navigation or
scrolling. Now queue_draw on filler overlays and gutters when the
current chunk changes. Fixed in both diff and merge views.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Scroll to cursor after toolbar undo/redo

The toolbar undo/redo buttons called buf.undo()/redo() without
scrolling the view, causing the viewport to jump away from the
edit location. Now calls scroll_mark_onscreen after each operation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update TESTING.md: vertical scroll is synced by chunk

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix find/replace: cross-pane navigation, Escape, macOS shortcut, undo

- Find next/prev now cycles across panes when no match in current pane
- Escape closes find bar from widget level (not just text views)
- Use Cmd+Shift+H for Find & Replace on macOS (Cmd+H is system Hide)
- Replace All uses delete+insert in a user action group so undo works

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix find next/prev cycling across panes

find_next_match wrapped within the same buffer, so cross-pane search
was never reached. Split into find_next_match_no_wrap with a
skip_current flag: current pane skips the cursor position, other
panes search from start/end without skipping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Clarify TESTING.md wording for Alt+Left/Right in dir file diff tab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update TESTING.md: directory tab shows dir names, not "Directory"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Show full absolute path in pane header tooltip

Canonicalize file_path so the tooltip always shows the full path
instead of a relative one when launched with relative arguments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Prevent duplicate file tabs in directory comparison

open_file_diff now checks if a tab with the same rel_path is already
open and switches to it instead of opening a duplicate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix TESTING.md: tab switching is Alt+1..9, not Ctrl+1..9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use native trash command on macOS instead of GIO

GIO's File::trash() silently deletes directories permanently on macOS
instead of moving them to Finder's Trash. Use the native macOS trash
command which works correctly. GIO is still used on Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix merge view chunk highlighting, conflict drawing, and navigation

- Add conflict-aware cursor detection: clicking conflict regions on
  left/right panes now highlights the conflict band via fallback to
  merged_gutter_chunks when chunk_at_cursor returns None
- Separate conflict drawing from regular chunk drawing: skip conflict
  chunks in draw_chunk_backgrounds via is_conflict flags, draw them
  with dedicated conflict stroke functions at full alpha
- Add bold border support for conflict regions (current chunk highlight)
- Fix navigation guard: prevent cursor_position_notify from overriding
  current_chunk during Alt+Up/Down navigation
- Collapse conflict bands into single navigation targets on side panes
  so Alt+Up/Down skips entire conflict band in one step
- Fix chunk map viewport indicators for merge view by redrawing on all
  three scroll adjustments
- Focus middle pane on 3-way merge open (CLI and new-comparison tab)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix scroll sync skipping Equal regions in line mapping

map_line_through_chunks advanced past Equal chunks without checking if
the source line fell inside them, causing the mapped position to jump
to the next non-equal chunk boundary. This made the left pane "stick"
in 3-way merge when left and middle had few differences. Now returns
the correct 1:1 mapped position when source line is within an Equal
region.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Track Save As directory changes in file watcher for diff and merge views

FileWatcher now supports dynamically adding watched paths via watch().
Both file_window and merge_view monitor save paths for directory changes
and tell the watcher to pick up new directories after Save As.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Show conflicting files in VCS window and open them in 3-way merge view

Add VcsStatus::Conflict for unmerged files (UU, AA, DD, DU, UD, AU, UA).
Conflict files display with red "Conflict" label in the VCS file list.
Opening a conflict launches a 3-way merge: ours (:2:) on left, working
copy in middle, theirs (:3:) on right.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.7.0

* Fix flaky UI tests by retrying keystrokes and polling for label changes

Centralize xdotool key-sending in conftest.py with a 200ms delay after
focus to let GTK process the event. Add send_keys_until() that retries
keystrokes up to 3 times if the expected label change doesn't appear
within the timeout (default 1s per attempt, 5s max).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* focus left pane on window show

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.6.1

Toggle v0.6.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Add tab navigation, file actions, new comparison, and blank comparison (

#33)

* Add Refresh, Open Externally, Save As, Save All, and dir context menu actions

- Refresh (Ctrl+R / F5): reload files from disk with unsaved-changes confirmation
- Open Externally (Ctrl+Shift+O): open focused file in system default app
- Save As (Ctrl+Shift+S): save focused pane to a new path, updating label
- Save All (Ctrl+Shift+L): save all dirty panes at once
- Dir context menu: add Open Externally, Collapse All, Expand All, Copy File Path
- Register accelerators in file, VCS, and merge windows
- Update keyboard shortcuts dialog and README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add tab navigation with Ctrl+Alt+PageUp/Down and Alt+1-9

Uses hardware keycodes for Alt+digit matching to avoid macOS input
method translating Alt+number into special characters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add New Comparison (Ctrl+N) with notebook tabs everywhere

Wrap file and merge windows in Notebook widgets so all window types
support tabs. Ctrl+N opens a "New Comparison" picker tab where users
can start file, directory, or 3-way merge comparisons — all opening
as tabs in the current window rather than new windows. Extract
build_dir_tab from build_dir_window to enable embedding directory
comparisons as tabs. Set all window titles to "Mergers" with specific
names shown in tab labels instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add Blank Comparison button to new comparison tab and welcome screen

Opens an empty 2-way file diff with "Untitled" labels. Each pane's
save button opens a pane-specific Save As dialog for blank paths;
after saving, the pane becomes file-backed and Ctrl+S saves normally.
Refresh and Open Externally are no-ops on blank panes, Save All skips
them.

The unsaved-changes confirmation dialog now processes saves
sequentially — async Save As dialogs pause the chain via
notify::sensitive and auto-resume, so each blank pane gets its own
Save As prompt when closing with unsaved changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.6.1

* Fix tab_path tracking, merge tab registration, VCS tab index, and reload regression

- Share tab_path Rc<RefCell<String>> between DiffPane and FileTab so Save As
  updates the path seen by close-tab unsaved checks (no more "Untitled" after save)
- Register merge tabs in open_tabs so window-close unsaved guard covers them
- Fix VCS "already open" tab lookup to use widget-based page_num instead of
  index arithmetic that breaks when non-file tabs are present
- Skip tabs with empty paths in reload_file_tab to prevent persistent reload
  failures from merge tabs in directory windows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address Copilot review: swap tab_path centrally, fix doc comment, normalize unsaved labels

- Swap tab_path alongside save_path in build_diff_view's swap handler so all
  consumers (including file_window) get correct FileTab paths after pane swap
- Remove redundant tab_path swaps from open_file_diff/open_file_diff_paths
  callbacks (now handled centrally, avoiding double-swap)
- Fix doc comment on build_new_comparison_tab: dirs and merge open as tabs,
  not new windows
- Normalize empty paths to "Untitled" in merge_view's window close-request
  unsaved collection, matching handle_notebook_close_request behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix window titles, dir tab watcher leak, and open_externally error reporting

- Set initial window title to comparison title in file_window and merge_view
  instead of hardcoded "Mergers"
- Stop dir tab filesystem watcher via connect_page_removed so it's cleaned up
  regardless of how the tab is closed (close button, Ctrl+W, window close)
- Check non-zero exit status in open_externally, not just spawn errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add UI tests verifying window title stays 'Mergers' after pane swap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

v0.6.0

Toggle v0.6.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Add preferences improvements and keyboard shortcuts dialog (#32)

* Update README with recent features

Add synchronized scrolling, wrap-around navigation, and chunk map
viewport indicator to the features list and comparison table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add editor and comparison preferences

Add insert spaces instead of tabs, show whitespace characters,
ignore blank lines, and ignore whitespace settings to the
preferences dialog. Per-view toggle buttons initialize from
saved defaults while remaining independently toggleable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bump to 0.6.0

* Use Cmd instead of Ctrl for shortcuts on macOS

Replace <Ctrl> with <Primary> in GTK accelerator strings so they
map to Cmd on macOS and Ctrl on Linux/Windows. Add a
PRIMARY_MODIFIER constant for EventControllerKey handlers and
update tooltip strings to show the platform-appropriate key name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use Cmd instead of Ctrl for shortcuts on macOS

Replace Ctrl with Cmd for all keyboard shortcuts on macOS via
set_platform_accels helper and has_primary_modifier for key event
handlers. Tooltips show the platform-appropriate modifier name.
Ctrl continues to work on Linux/Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add keyboard shortcuts dialog to preferences

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update README with keyboard shortcuts reference feature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>