diff --git a/doc/history/2026-09-14-6차.md b/doc/history/2026-09-14-6차.md
index a082d74..9e3d171 100644
--- a/doc/history/2026-09-14-6차.md
+++ b/doc/history/2026-09-14-6차.md
@@ -317,3 +317,57 @@ CP949 로 쓰이고 양쪽 한글이 다 읽히는지까지 본다.
A 의 다섯 가지(줄끝 · 반쯤 된 실패 · 상대 칸 · 지운 파일 · Both)가 모두 끝났다. 끝까지 풀어 커밋까지 가는 확인은 D-1 에서
처음부터 다시 한다. A 를 하다 B-1 으로 넘긴 것 - **새로고침 도중 게이트를 쥔 채 목록 다시 세우기가 불려 건너뛰는 뿌리** -
는 아직 그대로다(지금은 목록이 비지 않고 옛 목록이 남는다).
+
+
+---
+
+## B-1. 충돌 목록에서 3분할 화면까지 가는 길이 끊겨 있다 (2026-09-14, 완료)
+
+### 고치기 전에 다시 났다
+
+새로 만든 저장소에서 파일 비교 탭을 한 번 열어 둔 채 Git 탭으로 `feature/theirs` 를 병합했다(충돌 23개).
+
+| 본 것 | 값 |
+|---|---|
+| 파일 비교 탭 파일 목록 ([새로고침] 안 누름) | **304개** — 병합 전 커밋 파일 그대로. 충돌 23개가 아니다 |
+| 충돌 탭 목록을 두 번 누름 | **아무 일 없음** |
+| 충돌 탭 안내 글 · 병합 알림 | "**홈 탭**의 3분할 화면" · "**홈 탭**에서 해결하세요" — 그런 탭은 없다 |
+
+5차 C-1 때는 목록이 **0개** 였다. 6차 A-2 에서 "건너뛴 목록 불러오기가 목록을 비우지 않게" 막아서 이제는 **옛 목록이 남는다.**
+
+### 무엇이 원인이었나
+
+**목록.** 새로고침은 저장소 상태를 읽는 동안(게이트를 쥔 채) 화면 목록을 갈아 끼우고, 끝나자마자 Git 탭이 제 일로
+게이트를 다시 잡는다. 파일 비교 화면은 새로고침 신호를 받자마자 목록을 다시 세우려다 **"다른 작업 진행 중" 으로 건너뛰었다.**
+A-2 · A-4 에서는 저장한 뒤에만 기다렸다 다시 세우게 했고, 새로고침 자체는 그대로였다.
+
+**두 번 누르기.** 충돌 목록과 파일 비교 화면을 잇는 통로가 없었다. 폴더 비교 → 파일 비교에는 있던 것이다.
+
+### 고친 것
+
+| 자리 | 무엇 |
+|---|---|
+| `HomeViewModel.OnWorkspaceRefreshed` | 새로고침 뒤 목록 세우기를 **게이트가 풀릴 때까지 기다렸다** 한다(`RebuildListAfterRefreshAsync`). 막히면 몇 번 더 해 본다 |
+| `GitWorkspace.RequestOpenConflict` (새 통로) | "이 충돌 파일을 3분할로 열어 달라" 요청을 **남겨 두고** 파일 비교 탭으로 옮긴다. 폴더 비교의 넘겨주기와 같은 방식이다 - 파일 비교 화면은 탭을 처음 열 때 만들어지므로 받을 사람이 없을 수 있다 |
+| `HomeViewModel.OpenConflictAsync` | 요청을 받으면 목록에 그 파일이 설 때까지 기다렸다 고른다. 충돌 파일이라 A-3 대로 **네 칸**으로 열린다. 이 요청 때문에 화면이 새로 만들어졌으면 **기본 저장소를 새로 열지 않고** 지금 저장소로 목록만 세운다 |
+| Git 탭 충돌 목록 | 두 번 누르기 · Enter · **[3분할로 열기]** 단추. 이진 파일은 줄 단위로 못 본다고 알린다 |
+| 두 번 누르기 받는 법 | `MouseBinding` 이 아니라 목록의 `MouseDoubleClick` - 항목이 마우스 누름을 먼저 처리해 `MouseBinding` 은 항목 위에서 안 온다 |
+| 안내 글 · 병합 알림 | "…줄마다 고르려면 목록을 두 번 누르거나 [3분할로 열기] 를 누르세요. 파일 비교 탭에서 세 칸으로 열립니다." · "충돌 23건 - 충돌 탭에서 파일을 두 번 누르면 파일 비교 탭에서 세 칸으로 풉니다." |
+
+### 확인한 것 (새로 만든 저장소, MergeHub 로 병합)
+
+| 확인 | 결과 |
+|---|---|
+| 병합 알림 | "충돌 23건 - 충돌 탭에서 파일을 두 번 누르면…" (홈 탭 없음) |
+| 병합 뒤 파일 비교 탭, [새로고침] 안 누름 | 목록 **23개** |
+| 충돌 탭 안내 글 | 두 번 누르기 · [3분할로 열기] 를 알려 줌, 홈 탭 없음 |
+| 목록 두 번 누르기 | 파일 비교 탭으로 넘어가 `Io/Unit20.cs` 가 네 칸으로 열림, 충돌 잡힘 (약 1초) |
+| [3분할로 열기] | `Motion/Unit0.cs` 가 네 칸으로 (약 0.8초) |
+| Enter | `Io/LineEnding.cs` 가 네 칸으로 (약 1초) |
+| **파일 비교 탭을 한 번도 안 연 새 앱**에서 두 번 누르기 | 파일 비교 탭이 새로 만들어지며 `Io/Unit12.cs` 가 네 칸으로, 저장소는 `mergehub-c1` 그대로, 목록 23개 |
+| `git merge --abort` 뒤 **Git 탭에서만** [새로고침] | 파일 비교 탭 목록이 커밋 파일 304개로 돌아옴, REMOTE 칸 없음 |
+| 열기만 하는 동안 git 충돌 수 | 23 그대로 |
+
+화면 확인 **17건 통과**(Enter 1건은 처음에 목록 아래쪽 행을 골랐다가 자동 조작이 못 찾아 실패 - 가상화된 목록이라 화면 밖 행은
+안 만들어진다. 위쪽 행으로 다시 해 통과). 이번에 고친 파일은 검사 하네스가 쓰지 않아 앞의 785건이 그대로 유효하다.
+빌드 경고 0 · 오류 0.
diff --git a/doc/history/changelog.md b/doc/history/changelog.md
index faf0ff0..0b90182 100644
--- a/doc/history/changelog.md
+++ b/doc/history/changelog.md
@@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
+| 2026-09-14 | **6차 B-1** — 새로고침 뒤 파일 목록을 게이트가 풀릴 때까지 기다렸다 세움(병합 뒤 304개 → 23개). 충돌 목록 두 번 누르기·Enter·[3분할로 열기] 로 파일 비교 탭 네 칸에서 연다. 없는 '홈 탭' 글을 고침 | 아래 |
| 2026-09-14 | **6차 A-5** — 파일 단위 Accept Both 가 두 벌을 글로 풀어 이어 붙이고 내 쪽 형식으로 쓴다(가운데 BOM 사라짐). 누르기 전에 무엇이 일어나는지 묻는다. A 묶음 끝 | 아래 |
| 2026-09-14 | **6차 A-4** — 한쪽이 지운 충돌(UD·DU)은 줄 단위 3분할 대신 남은 파일을 두 칸으로 보이고 [파일 남기기] · [파일 지우기] 로 푼다. 거꾸로 세던 범례(Theirs only 89)가 없어짐 | 아래 |
| 2026-09-14 | **6차 A-3** — 병합 중 충돌 파일을 열면 콤보를 안 건드려도 네 칸이 뜬다. REMOTE 제목에 상대 브랜치 이름(MERGE_HEAD → 병합 메시지 → 짧은 해시) | 아래 |
diff --git a/doc/진행상황.md b/doc/진행상황.md
index f21e52e..7da58d8 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -103,7 +103,7 @@
| 상태 | 항목 |
|---|---|
-| 대기 | B-1 충돌 목록에서 3분할 화면까지 가는 길이 끊겨 있다 |
+| **완료** | B-1 충돌 목록에서 3분할 화면까지 가는 길이 끊겨 있다 — 병합 뒤 목록이 바로 서고, 두 번 누르기·Enter·[3분할로 열기] |
| 대기 | B-2 확인 창이 엉뚱한 파일 이름을 댄다 |
| 대기 | B-3 커밋 메시지가 엉뚱하게 남는다 |
| 대기 | B-4 MERGED 칸 줄 번호가 네 자리에서 잘린다 |
diff --git a/src/MergeHub/Services/GitWorkspace.cs b/src/MergeHub/Services/GitWorkspace.cs
index 5208c25..e251075 100644
--- a/src/MergeHub/Services/GitWorkspace.cs
+++ b/src/MergeHub/Services/GitWorkspace.cs
@@ -523,6 +523,40 @@ namespace MergeHub.Services
PlainCompareRequested.Invoke(pending.Left, pending.Right);
}
+ // ================= 충돌 파일을 파일 비교 탭에서 열기 (6차 B-1) =================
+
+ ///
+ /// "이 충돌 파일을 3분할로 열어 달라" - Git 탭의 충돌 목록이 보내고 파일 비교 탭이 받는다.
+ ///
+ /// 예전에는 충돌 목록을 두 번 눌러도 아무 일이 없었다. 줄마다 고르려면 파일 비교 탭으로 가서
+ /// [새로고침] 을 누르고 콤보에서 같은 파일을 다시 찾아야 했다.
+ ///
+ public event Action? OpenConflictRequested;
+
+ /// 아직 아무도 받지 않은 요청. 파일 비교 화면은 탭을 처음 열 때 만들어지므로 남겨 둔다.
+ private string? _pendingOpenConflict;
+
+ public bool HasPendingOpenConflict => _pendingOpenConflict is not null;
+
+ public void RequestOpenConflict(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path)) return;
+
+ _pendingOpenConflict = path;
+ OnUi(DeliverPendingOpenConflict);
+ ActivateWhenReady(MergeHub.ViewIds.Home);
+ }
+
+ /// 남아 있는 요청을 넘긴다. 파일 비교 화면이 새로 만들어지면 스스로 한 번 불러 준다.
+ public void DeliverPendingOpenConflict()
+ {
+ if (_pendingOpenConflict is not { } path) return;
+ if (OpenConflictRequested is null) return;
+
+ _pendingOpenConflict = null;
+ OpenConflictRequested.Invoke(path);
+ }
+
// ================= 켤 때 넘어온 인자 =================
/// 폴더 비교 화면이 아직 없을 때 남겨 두는 요청. 파일 비교 쪽과 같은 방식이다.
diff --git a/src/MergeHub/ViewModels/FolderViewModel.cs b/src/MergeHub/ViewModels/FolderViewModel.cs
index 6730795..7386ba1 100644
--- a/src/MergeHub/ViewModels/FolderViewModel.cs
+++ b/src/MergeHub/ViewModels/FolderViewModel.cs
@@ -148,7 +148,7 @@ namespace MergeHub.ViewModels
else Summary = "오른쪽 폴더를 고르고 비교를 누르세요.";
}
- /// 홈 탭에서 이 두 파일을 비교해 달라고 알린다.
+ /// 파일 비교 탭에서 이 두 파일을 비교해 달라고 알린다.
public event EventHandler<(string Left, string Right)>? OpenInDiffRequested;
public ObservableCollection Nodes { get; } = new ObservableCollection();
diff --git a/src/MergeHub/ViewModels/GitViewModel.cs b/src/MergeHub/ViewModels/GitViewModel.cs
index 32fd4d2..ea1e647 100644
--- a/src/MergeHub/ViewModels/GitViewModel.cs
+++ b/src/MergeHub/ViewModels/GitViewModel.cs
@@ -237,7 +237,7 @@ namespace MergeHub.ViewModels
CommitMessage = $"Merge branch '{branch}' into {_workspace.CurrentBranch}";
if (result.HasConflicts)
- MessageUtil.ShowWarningToast($"충돌 {result.Conflicts.Count}건 - 홈 탭에서 해결하세요.");
+ MessageUtil.ShowWarningToast($"충돌 {result.Conflicts.Count}건 - 충돌 탭에서 파일을 두 번 누르면 파일 비교 탭에서 세 칸으로 풉니다.");
else
MessageUtil.ShowToast(result.Message);
});
@@ -285,6 +285,25 @@ namespace MergeHub.ViewModels
return go ? ResolveAsync(ConflictResolution.Both) : Task.CompletedTask;
}
+ ///
+ /// 고른 충돌 파일을 파일 비교 탭의 3분할로 연다(6차 B-1). 목록을 두 번 누르거나 Enter 로도 온다.
+ /// 켜짐/꺼짐을 따로 두지 않는다 - 고른 것이 없으면 아무 일도 안 한다. 이진 파일은 줄 단위로 볼 수 없어 알린다.
+ ///
+ [RelayCommand]
+ private void OpenConflictInCompare()
+ {
+ var conflict = SelectedConflict;
+ if (conflict is null) return;
+
+ if (conflict.IsBinary)
+ {
+ MessageUtil.ShowWarningToast($"이진 파일이라 줄 단위로 볼 수 없습니다. 아래 Accept 단추로 한쪽을 고르세요: {conflict.Path}");
+ return;
+ }
+
+ _workspace.RequestOpenConflict(conflict.Path);
+ }
+
private Task ResolveAsync(ConflictResolution resolution) =>
_workspace.RunAndRefreshAsync($"충돌 해결({resolution})",
() => _workspace.Git.ResolveConflict(SelectedConflict!.Path, resolution));
diff --git a/src/MergeHub/ViewModels/HomeViewModel.cs b/src/MergeHub/ViewModels/HomeViewModel.cs
index a4d76f6..ba1bbc0 100644
--- a/src/MergeHub/ViewModels/HomeViewModel.cs
+++ b/src/MergeHub/ViewModels/HomeViewModel.cs
@@ -36,6 +36,7 @@ namespace MergeHub.ViewModels
_workspace.Refreshed += OnWorkspaceRefreshed;
_workspace.StateChanged += RaiseCanExecuteChanged;
_workspace.PlainCompareRequested += OnPlainCompareRequested;
+ _workspace.OpenConflictRequested += OnOpenConflictRequested;
_workspace.Recent.Changed += (_, _) => _workspace.OnUi(RefreshRecent);
_workspace.QueueChanged += (_, _) => RefreshQueue();
@@ -50,6 +51,16 @@ namespace MergeHub.ViewModels
IsGitMode = _workspace.IsRepositoryOpen;
RefreshRemoteBranches();
+ // Git 탭의 충돌 목록에서 파일을 두 번 눌러 이 화면이 처음 만들어진 경우(6차 B-1).
+ // 저장소는 Git 탭이 이미 열었다. 아래의 기본 저장소 열기로 가면 다른 저장소를 열 수 있다 -
+ // 지금 저장소로 목록만 세우고 그 파일을 연다.
+ if (_workspace.HasPendingOpenConflict)
+ {
+ OnWorkspaceRefreshed();
+ _workspace.DeliverPendingOpenConflict();
+ return;
+ }
+
// 폴더 비교에서 파일을 골라 이 화면이 만들어진 경우라면 그 파일을 먼저 연다.
// 이 화면은 탭을 처음 열 때 만들어지므로, 요청이 우리보다 먼저 도착해 있을 수 있다.
if (_workspace.HasPendingPlainCompare)
@@ -868,10 +879,57 @@ namespace MergeHub.ViewModels
OnPropertyChanged(nameof(SelectedCommit));
RaiseCommitDetail();
- _ = LoadCommitAsync();
+ _ = RebuildListAfterRefreshAsync();
// 여기서 다시 세우는 것은 사용자가 고른 것이 아니다. LoadCommitAsync 안에서 묻지 않게 한다.
}
+ private void OnOpenConflictRequested(string path) => _ = OpenConflictAsync(path);
+
+ ///
+ /// Git 탭 충돌 목록에서 고른 파일을 연다(6차 B-1). 충돌 파일이므로 A-3 대로 세 칸으로 열린다.
+ ///
+ /// 목록에 그 파일이 아직 없을 수 있다 - 병합 직후라 새로고침 뒤 목록 세우기가 아직 안 끝났거나,
+ /// 이 화면이 방금 만들어졌다. 게이트가 풀릴 때까지 기다렸다 목록을 세우고 나서 고른다.
+ /// 고르는 일은 에 맡긴다 - 저장 안 한 편집이 있으면 거기서 묻는다.
+ ///
+ private async Task OpenConflictAsync(string path)
+ {
+ if (!_workspace.IsRepositoryOpen) return;
+ IsGitMode = true;
+
+ for (var attempt = 0; attempt < 5 && !Files.Contains(path); attempt++)
+ {
+ await _workspace.WhenIdleAsync();
+ await LoadCommitAsync();
+ }
+
+ if (!Files.Contains(path))
+ {
+ MessageUtil.ShowWarningToast($"파일 목록에서 찾지 못했습니다: {path}");
+ return;
+ }
+
+ if (!string.Equals(SelectedFile, path, StringComparison.Ordinal))
+ SelectedFile = path;
+ }
+
+ ///
+ /// 새로고침 뒤 파일 목록을 반드시 다시 세운다(6차 B-1).
+ ///
+ /// 새로고침은 저장소 상태를 읽는 동안(게이트를 쥔 채) 화면 목록을 갈아 끼우고, 끝나자마자 Git 탭이 제 일로
+ /// 게이트를 다시 잡는다. 그 틈에 목록 세우기를 부르면 "다른 작업 진행 중" 으로 건너뛰었다.
+ /// 병합하고 파일 비교 탭으로 오면 목록이 병합 전 커밋 파일(304개) 그대로였고, [새로고침] 을 한 번 더 눌러야 충돌 23개가 나왔다.
+ /// 게이트가 풀릴 때까지 기다렸다가 세우고, 또 막히면 몇 번 더 해 본다.
+ ///
+ private async Task RebuildListAfterRefreshAsync()
+ {
+ for (var attempt = 0; attempt < 5; attempt++)
+ {
+ await _workspace.WhenIdleAsync();
+ if (await LoadCommitAsync()) return;
+ }
+ }
+
/// 선택 커밋이 담고 있는 파일 목록을 채우고, 가능하면 이전 선택을 유지한다.
///
/// 목록을 다시 세웠거나 세울 것이 없으면 true. 게이트에 막혀 건너뛰었으면 false -
diff --git a/src/MergeHub/Views/GitView.xaml b/src/MergeHub/Views/GitView.xaml
index 6ef0d54..998d6ed 100644
--- a/src/MergeHub/Views/GitView.xaml
+++ b/src/MergeHub/Views/GitView.xaml
@@ -240,11 +240,18 @@
+ Text="파일 전체를 한쪽으로 채택하려면 아래 버튼을 쓰고, 줄마다 고르려면 목록을 두 번 누르거나 [3분할로 열기] 를 누르세요. 파일 비교 탭에서 세 칸으로 열립니다."/>
+
+
+
+
@@ -271,6 +278,8 @@
+
diff --git a/src/MergeHub/Views/GitView.xaml.cs b/src/MergeHub/Views/GitView.xaml.cs
index 5cd0fb5..a0744b1 100644
--- a/src/MergeHub/Views/GitView.xaml.cs
+++ b/src/MergeHub/Views/GitView.xaml.cs
@@ -36,6 +36,14 @@ namespace MergeHub.Views
}
// PasswordBox 는 보안상 Password 를 바인딩할 수 없어 코드 비하인드로 전달한다.
+ /// 충돌 목록을 두 번 누르면 파일 비교 탭의 3분할에서 연다(6차 B-1). 항목 위가 아니면 무시한다.
+ private void Conflicts_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ if (e.OriginalSource is not System.Windows.DependencyObject hit) return;
+ if (System.Windows.Controls.ItemsControl.ContainerFromElement((System.Windows.Controls.ItemsControl)sender, hit) is null) return;
+ if (DataContext is MergeHub.ViewModels.GitViewModel vm && vm.OpenConflictInCompareCommand.CanExecute(null))
+ vm.OpenConflictInCompareCommand.Execute(null);
+ }
private void Password_Changed(object sender, RoutedEventArgs e)
{
if (sender is PasswordBox box && ViewModel is { } vm) vm.Workspace.Password = box.Password;