# 작업 지시 — MergeHub 사용성 보강 (2차)
대상: `D:\Sorce\MergeTool\MergeHub` (플러그인) · `D:\Sorce\MergeTool\UbisamPlatform` (셸)
전제: 1차 지시서([머지툴보강.md](머지툴보강.md))의 작업 1~4 · 1-1~1-3 은 모두 끝난 상태다.
---
## 공통 규칙
- **항목 하나를 끝낼 때마다 빌드하고 실제로 실행해 화면을 확인한 뒤 보고할 것.** 전부 끝내고 한 번에 보고하지 말 것.
- 기존 테스트 하네스(Align 37 · Move 11 · Editor 14 · Rules 21 · Sanity 20 · Token 15 · Note 16 · ThreeWay 20 · Lang 40 · Folder 27 = **221건**)는 계속 통과해야 한다. 로직을 건드리면 테스트를 먼저 늘린다.
- 빌드 경고 0 · 오류 0 을 유지한다.
- 플랫폼(`UbisamPlatform`)을 고치면 **다른 소비 프로그램에 미치는 영향**을 함께 적고 `doc/history/changelog.md` 에 남긴다.
---
## 1. 사용자 이름 등록
### 1-1. 첫 실행 시 이름 입력
- 프로그램을 켰을 때 **등록된 사용자 이름이 없으면** 이름을 묻는 창을 띄운다.
- 입력값은 `D:\UbisamConfig` 아래에 저장한다.
- 이미 등록돼 있으면 창을 띄우지 않는다. **매번 뜨면 안 된다.**
- 설정 화면에서 언제든 바꿀 수 있어야 한다(지금의 `MergeHub > 머지 주석 > 작성자` 칸이 그 자리다).
- 빈 값으로 넘어가려 하면 막거나, 넘어가더라도 다음 실행에 다시 묻는다.
### 1-2. 머지 주석이 이 이름을 쓴다
현재 `GitWorkspace.ResolveAuthorName()` 이 **설정값 → `git config user.name` → 윈도우 계정** 순으로 고른다.
1-1 에서 등록한 이름이 이 "설정값" 자리에 들어가면 된다 — **순서 자체는 그대로 두는 것이 맞다**(설정을 비워 두면 Git 설정을 따라가는 지금 동작이 쓸모 있다).
**판정 기준**: 이름을 처음 등록하고 아무 줄이나 반영하면 주석에 그 이름이 나온다. 프로그램을 다시 켜도 묻지 않는다.
---
## 2. 머지 주석을 "쌓이게" 바꾸기
### 2-1. 지금 동작
`Services/MergeNote.cs` 가 **줄 끝에** 주석을 붙인다(`Append`). 그 파일 주석에 적힌 이유는:
> 줄 "위"가 아니라 "끝"에 붙인다 - BASE/LOCAL 을 같은 줄 번호로 나란히 보여주는 구조라
> 줄을 새로 끼워 넣으면 두 패널의 정렬이 통째로 어긋난다.
**이 제약이 지금도 유효한지 먼저 확인하라.** 그동안 정렬기(histogram)와 필러 처리가 바뀌었으므로, 줄을 끼워 넣어도 정렬이 스스로 맞을 수 있다. 유효하다면 어떻게 우회할지 설계부터 적고 시작한다.
### 2-2. 바꿀 동작
- 주석을 **그 줄 위에 별도 줄로** 넣는다(줄 끝 X).
- 같은 줄을 다시 반영하면 **기존 주석을 지우지 않고 새 주석을 추가**한다.
- 쌓이는 순서는 **새 주석이 코드 줄 바로 위**, 오래된 주석일수록 위로 밀린다.
```
// [hong] 2026-09-01 1차 반영 ← 가장 오래된 것이 맨 위
// [kim] 2026-09-05 재확인
// [lee] 2026-09-07 최종 ← 가장 최근이 코드 바로 위
laser.SetPower(2000);
```
- 주석 기호는 지금처럼 언어를 따른다(`MergeNote.CommentTokensFor`). **`LanguageProfile` 이 이미 언어별 주석 기호를 들고 있으므로 둘 중 하나로 합치는 것을 검토하라** — 같은 지식이 두 곳에 있으면 언젠가 어긋난다.
### 2-3. 반드시 확인할 것
- 주석 줄이 늘어나도 **BASE ↔ LOCAL(↔ REMOTE ↔ MERGED) 행이 어긋나지 않아야 한다.**
- 저장했다가 다시 열었을 때 주석이 **중복으로 다시 붙지 않아야 한다.**
- 되돌리기(Undo · Revert · 전체 되돌리기)로 주석도 함께 사라져야 한다.
- `NoteTest` 하네스(16건)를 이 동작에 맞게 늘린다.
---
## 3. 차이 이동에서 행이 안 맞는 문제 (결함)
`이전 차이` · `다음 차이` · `처음` · `마지막` 을 누르면 **행 위치가 어긋난다.**
- 재현 조건부터 정확히 적어라 — 워드랩 켬/끔, 2-Way/3-Way, 필러 줄이 있는 경우 각각.
- 관련 코드: `HomeViewModel` 의 `_diffStarts` · `ComputeDiffBlockStarts` · `GoToRow`,
`HomeView.OnScrollToRowRequested`(현재 `LocalEditor.ScrollToLine(row - 3)` 으로 위 여유 3줄을 둔다).
- **의심 지점**: `ScrollToRowRequested` 는 편집기의 **줄 인덱스**로 스크롤하는데, 표시층은 ListBox 의 **픽셀**로 따라간다. 워드랩이 켜져 있으면 한 행이 여러 시각 줄이라 둘이 어긋난다.
- 고친 뒤에는 **선택된 행이 화면 안에 들어오고**, 세 패널(또는 네 패널)이 같은 행에 서 있어야 한다.
**판정 기준**: 워드랩을 켠 3-Way 상태에서 `처음`→`다음 차이`를 끝까지 눌러도 매번 그 차이 줄이 화면에 보이고 네 패널이 같은 줄을 가리킨다.
---
## 4. WinMerge 단축키
### 4-1. 적용
지금 있는 것: `Alt+↑/↓`(이전/다음 차이) · `Ctrl+Home/End`(처음/마지막) · `Ctrl+G`(줄 이동) · `F2`(북마크).
WinMerge 기준으로 맞추고 빠진 것을 채운다. 최소한 아래는 넣는다.
| 키 | 동작 |
|---|---|
| `Alt+↓` / `Alt+↑` | 다음 / 이전 차이 |
| `Alt+Home` / `Alt+End` | 첫 / 마지막 차이 |
| `Alt+←` / `Alt+→` | 왼쪽→오른쪽 / 오른쪽→왼쪽 반영 (3-Way 에서는 Take Mine / Take Theirs) |
| `Ctrl+S` | 저장 |
| `F5` | 다시 비교 |
| `F7` / `Shift+F7` | 다음 / 이전 차이(대체 키) |
| `Ctrl+G` | 줄 이동 |
| `F2` | 다음 북마크 |
| `Ctrl+F` | 찾기 (없으면 **이 항목은 범위 밖으로 빼고 그렇다고 보고**할 것) |
- 편집기가 먼저 삼키는 키가 있으므로 `PreviewKeyDown` 에서 가로챈다(지금 방식 유지).
- **기존 편집 동작을 뺏으면 안 된다** — 예: `Ctrl+A`, `Ctrl+Z` 는 편집기 것이다.
- 실제로 WinMerge 와 다르게 정한 것이 있으면 **왜 다른지 적어라.**
### 4-2. 단축키 설명 창
- **우측 상단에 버튼**을 두고, 누르면 단축키 목록 창이 뜬다.
- "우측 상단"이 어디인지 확인할 것 — 지금 화면에서 그 자리는 **ACTIONS 패널 위쪽**이거나 **상단 툴바 오른쪽 끝**이다. 둘 중 눈에 띄고 다른 것을 밀어내지 않는 자리를 고르고 이유를 적는다.
- 목록은 **코드에 박은 문자열이 아니라 실제 키 처리와 한 곳에서 나와야 한다** — 둘이 갈라지면 설명이 거짓말이 된다.
---
## 5. 소스가 더 잘 보이게 — 상·하단 줄이기 (플랫폼)
**이건 플랫폼 변경이라 다른 소비 프로그램에 모두 영향이 간다. 먼저 영향 범위를 적고 시작할 것.**
### 5-1. 상단 브랜딩 바
`ShellWindow.xaml` — `Padding="26,14"`, 로고 `Height="30"`, 장비명 `FontSize="18"`, 시계 `FontSize="25"`.
### 5-2. 하단 탭 바
`ShellStyles.xaml` 의 `Ubisam.Style.SidebarItem.Horizontal` — `Padding="30,13,30,14"`, 아이콘 `22x22`, 아이콘 아래 여백 `7`, 글자 `13.5`.
### 5-3. 방향
- 두 줄을 합쳐 **세로로 최소 40~60px 은 벌어야** 코드가 눈에 띄게 더 보인다.
- 다만 **터치로도 누르는 장비**라는 점을 잊지 말 것. 탭은 손가락으로 누를 수 있는 크기를 유지해야 한다 — 무작정 줄이면 안 된다.
- 가능하면 **크기를 상수로 박지 말고** 지금의 화면 배율(`UiSettingsService.ContentScale`)처럼 한 곳에서 정하도록 빼라. 장비마다 다르게 쓸 여지가 생긴다.
- 줄이기 전후의 **코드 표시 줄 수를 실제로 세어** 얼마나 늘었는지 숫자로 보고할 것.
---
## 진행 순서
```
1 사용자 이름 등록 (독립적, 먼저 끝내면 다른 작업의 주석 확인이 쉬워진다)
3 차이 이동 행 어긋남 (결함 — 다른 작업 확인에 계속 걸린다)
2 머지 주석 쌓기 (정렬에 영향이 크므로 3 이 끝난 뒤)
4 단축키 + 설명 창
5 상·하단 줄이기 (플랫폼)
```
2번을 3번 뒤로 미룬 이유 — 주석 줄이 늘어나면 행 정렬이 흔들리는데, 그 상태에서 차이 이동 결함까지 겹치면 무엇이 원인인지 가릴 수 없다.
---
## 결정 사항 (2026-09-07, 사용자 확인 완료)
1. **주석은 고친 줄 위에 올린다.** 새 주석이 코드 줄 바로 위에 붙고 기존 주석은 위로 밀린다.
정렬 제약은 우회해서라도 이 동작으로 간다.
2. **사용자 이름은 플랫폼의 "현재 사용자"로 둔다.** 다른 소비 프로그램도 쓸 수 있어야 한다.
3. **찾기(`Ctrl+F`) 를 포함해 머지툴에 맞는 단축키를 전부 넣는다.** 찾기 기능 자체도 만든다.
4. **터치는 필수가 아니다.** 상·하단을 줄인다.
---
## 진행 기록
| 날짜 | 항목 | 결과 |
|---|---|---|
| 2026-09-07 | 문서 작성 | 착수 전 |
| 2026-09-07 | **1. 사용자 이름 등록** | **완료.** 플랫폼에 `CurrentUserService` 신설(`D:\UbisamConfig\Settings\CurrentUserService.json`). 이름이 없으면 첫 실행에 `UserNameDialog` 로 한 번만 묻는다(취소 버튼 없음 - 넘어가면 다음 실행에 또 물어야 하고, 그러면 "매번 뜨는 성가신 창"이 된다). 소비 프로그램은 DI 로 주입받아 쓴다. `GitWorkspace.ResolveAuthorName()` 순서는 **MergeHub 설정 → 플랫폼 사용자 → git config → 윈도우 계정**. 플랫폼 사용자를 git config 보다 앞에 둔 이유 - 사람이 직접 적어 넣은 이름이고, 장비를 여럿이 쓰는 현장에서는 그게 실제 작업자다. **곁들여 고친 것**: 작성자 이름을 저장소를 읽을 때만 정하고 있어서, **Git 없이 파일 두 개를 비교하면 이름이 비고 주석에 윈도우 계정이 찍혔다.** 생성 시점에 한 번 정하고 설정에서 바꾸면 따라가게 했다. **실행 확인**: 이름 없이 켜면 창이 뜨고, `김상준` 등록 뒤 다시 켜면 안 뜬다. 주석 미리보기가 `// [김상준] 2026-09-07`. |
| 2026-09-07 | **3. 차이 이동 행 어긋남** | **완료.** 원인 둘. ① `OnScrollToRowRequested` 가 **논리 행 번호를 "화면에 보이는 줄" API**(`GetCharacterIndexFromLineIndex`)에 그대로 넘기고 있었다. 워드랩이 켜지면 한 행이 여러 줄로 접히므로 캐럿이 딴 줄로 갔다 - 실제로 상태바는 `6행`인데 화면에서 선택된 줄은 8행이었고 **아래로 갈수록 더 벌어졌다**(9행에서는 2줄 차이). 글에서 직접 세어 논리 행 → 글자 위치로 바꾸도록 고쳤다. ② 그렇게 고쳐도 **화면이 안 굴러갔다** - `TextBox.ScrollToLine` 이 이 화면(편집기가 세 겹의 스크롤 주인)에서는 듣지 않아, `마지막`을 눌러도 맨 위에 남았다. 캐럿의 실제 좌표를 재서 스크롤뷰어를 직접 옮기도록 바꿨다(워드랩이든 아니든 같은 방식). **실행 확인**: 워드랩 켠 상태로 `처음`→`다음 차이`를 끝까지, `마지막`, `이전 차이`까지 눌러도 상태바 행과 화면에서 선택된 행이 매번 일치(5↔4, 8↔7, 10↔9, 27↔26, 38↔37). |
| 2026-09-07 | **2. 머지 주석을 위로 쌓기** | **완료.** 주석을 줄 끝이 아니라 **고친 줄 바로 위에 한 줄로** 넣는다. 코드 줄의 들여쓰기를 그대로 따라간다. 같은 줄을 다시 반영하면 새 주석이 코드 바로 위에 들어가고 **앞서 남긴 주석은 위로 밀린다**(지우지 않는다). 정렬은 저절로 맞는다 - 끼워 넣은 줄은 "LOCAL 에만 있는 줄"이라 이미 있던 추가 줄 처리대로 BASE 쪽에 빈 자리가 생긴다. `MergeNote.cs` 에 적혀 있던 "줄을 끼워 넣으면 정렬이 어긋난다"는 제약은 그동안 정렬기와 필러 처리가 바뀌어 **더는 유효하지 않았다.** **여기서 결함 하나를 잡았다**: 주석 줄이 BASE 에 없는 줄이라 차이로 잡혔고, 그래서 **다음 Accept 나 전체 반영이 방금 남긴 주석을 지워 버렸다**("BASE 를 따른다 = BASE 에 없는 줄은 없앤다" 규칙 때문). `MergeLine.IsMergeNote` 를 두어 주석 줄은 차이로 세지도, 반영 대상이 되지도 않게 했다. Undo/Redo/Revert 는 `LineEdit.InsertedNote` 로 주석 줄까지 함께 넣고 뺀다 - 본문만 되돌리고 주석을 남기면 파일에 유령 주석이 쌓인다. **실행 확인**: 전체 되돌리기 43줄·주석 0 → 1회 반영 44줄·주석 1(반영한 줄 바로 위) → Undo 43줄·주석 0 → Redo 44줄·주석 1. 전체 반영은 반영한 줄마다 각자 위에 주석이 붙는다. |
| 2026-09-07 | **4. WinMerge 단축키 + 설명 창** | **완료.** 키 표를 `Services/ShortcutMap.cs` 한 곳으로 모았다 — 키 처리(`HomeView.View_PreviewKeyDown` → `RunShortcut`)와 설명 창(`Views/ShortcutHelpWindow`)이 **같은 목록**을 읽으므로 설명이 실제 동작과 갈라질 수 없다. 넣은 키: `Alt+↓/↑`·`F4`/`Shift+F4`(다음·이전 차이), `Alt+Home/End`·`Ctrl+Home/End`(첫·마지막 차이), `Alt+→/←`(왼→오른·오른→왼 반영), `Ctrl+Z`/`Ctrl+Y`, `Ctrl+M`(북마크 설정/해제)·`Alt+PageDown`·`F2`(다음 북마크), `Ctrl+G`, `Ctrl+D`(폴더 비교), `F5`·`Ctrl+R`, `Ctrl+S`, `Ctrl+F`·`F3`/`Shift+F3`·`Esc`, `F1`(이 목록). **찾기 기능 자체도 만들었다** — 편집기 위에 떠 있는 찾기 칸(패널 머리·바닥에 끼우면 열 때마다 BASE 와 높이가 어긋난다), 대소문자 구분, 앞뒤로 한 바퀴 돌기, 몇 개인지 표시. 처리는 편집기가 아니라 **화면 전체(`UserControl.PreviewKeyDown`)** 에 건다 — 편집기에만 걸면 파일 목록이나 찾기 칸에 초점이 있을 때 단축키가 죽는다. **WinMerge 와 다르게 정한 것 3가지**: ① `Ctrl+W`(탭 닫기)는 넣지 않았다 — 탭이 고정이라 닫을 것이 없고, 아무 일도 않는 키는 없느니만 못하다. ② `Ctrl+D` 는 새 창이 아니라 폴더 비교 **탭**으로 간다 — 창을 여러 개 띄우는 도구가 아니다. ③ `Ctrl+Z` 는 머지 반영을 되돌리되, **되돌릴 머지 작업이 없으면 키를 먹지 않아** 편집기의 글자 되돌리기가 그대로 산다(`Run()` 이 `CanExecute` 를 보고 판단). `Alt` 를 누르면 WPF 가 `e.Key` 에 `Key.System` 을 넣고 진짜 키를 `e.SystemKey` 로 보내므로 그것부터 풀어 준다. 버튼은 **상단 상태 바 오른쪽 끝**에 뒀다 — 그 줄이 내용에 따라 늘었다 줄었다 하는 `WrapPanel` 이라 그대로 넣으면 자리가 떠다녀서, `DockPanel` 로 감싸 오른쪽에 붙박았다. `ShortcutTest`(58건) 신설: 키 중복·풀림·편집기 몫 침범 여부·표기·묶음 순서·WinMerge 핵심 키 존재를 본다. **실행 확인**: `Alt+Home`→첫 차이(5행), `Alt+↓`→다음 차이(8행), `Alt+→`→그 줄 반영(주석 포함, 머지 반영함 1), `Ctrl+Z`→되돌림(머지 반영함 0·Redo 켜짐). `Ctrl+F` 로 "Polarize" 찾기 3개, 27행 20열로 이동. `F1`/버튼 둘 다 목록 창이 뜬다. |
| 2026-09-07 | **5. 상·하단 줄이기 (플랫폼)** | **완료.** 크기를 XAML 안에 박아 두지 않고 `ShellStyles.xaml` 에 `Ubisam.Metric.BrandingBar.*` · `Ubisam.Metric.NavItem.*` 로 빼서, 소비 프로그램이 `Application.Current.Resources` 에서 같은 키를 덮어쓰면 장비마다 다르게 쓸 수 있다(기존 `Ubisam.Metric.ContentPadding` 과 같은 방식). 값: 브랜딩 바 여백 `26,14`→`26,7`, 로고 30→24, 구분선 24→18, 장비명 18→15, 시계 25→19 / 탭 여백 `30,13,30,14`→`30,6,30,7`, 아이콘 22→19, 아이콘 아래 여백 7→3, 글자 13.5→12.5. **줄 수 확인(2560×1600 @150%, 최대화)**: 하단 탭 바 135px→95px, **코드 편집 영역 958px→1038px(+80px)**, 코드 표시 줄 **31줄 → 34줄(+3줄)**. 화면 배율 100% 기준 약 53 DIP 를 벌었다(목표 40~60). **다른 소비 프로그램 영향**: 플랫폼 전체에 걸리므로 모든 프로그램의 상·하단 띠가 같이 얇아진다. 터치 장비라면 위 세 값(`NavItem.Padding`·`IconSize`·`FontSize`)만 되돌리면 원래 크기다. |
| 2026-09-07 | **곁들여 고친 것 3가지** | ① **켤 때마다 이름 확인**: `UserNameDialog.AskIfMissing` → `Ask`. 한 장비를 여러 사람이 쓰므로 앞사람 이름으로 기록이 남으면 안 된다. **지난번 이름이 미리 채워지고 전체 선택돼 있어 같은 사람이면 Enter 한 번**이다. ② **마우스 가로 휠**: WPF 에는 가로 휠 이벤트가 아예 없어(`MouseWheel` 은 세로뿐) 창 메시지 `WM_MOUSEHWHEEL`(0x020E) 을 직접 후킹했다. **여기서 결함 하나를 더 잡았다** — 가로로 밀 때 **BASE·REMOTE·LOCAL 원본 패널이 따라오지 않았다.** 표시층 ListBox 를 가로로 굴리려 했는데, 행이 칸 너비에 맞춰 늘어나 굴릴 여지(`ScrollableWidth`)가 0 이라 아무 일도 일어나지 않았던 것. LOCAL 처럼 **글자만 `TextShiftX` 만큼 미는 방식**으로 네 패널을 통일했다(여백·줄번호는 제자리). ③ **아래로 스크롤한 상태에서 전체 되돌리기를 하면 글자가 위로 밀리던 것**: `AlignDecorationToEditor` 가 편집기의 **0번째 글자**를 표시층의 **처음 그려진 행**과 견주고 있었다. 가상화 때문에 스크롤한 뒤에는 그 행이 0행이 아니라서, 스크롤한 거리만큼 어긋난 보정값이 들어갔다. 컨테이너에서 실제 행 번호를 얻어 같은 줄끼리 견주도록 고쳤다. **실행 확인**: 전체 반영 후 아래로 스크롤 → 전체 되돌리기 → 줄 번호와 코드가 모든 행에서 일치. |
| 2026-09-07 | **가로 휠 — 3-Way 확인 중 결함 발견·수정** | 3-Way 에서 가로 휠이 **아무 일도 하지 않았다.** 스크롤 주인인 MERGED 는 폭 전체를 쓰므로 밀 여지가 없는데(`ScrollableWidth` 0), 정작 잘려 보이는 것은 위의 좁은 원본 세 칸(LOCAL(Mine)·BASE·REMOTE)이었기 때문이다. 게다가 그 세 칸에는 **각자 따로 노는 가로 스크롤 막대**가 달려 있어, 끌면 줄 번호까지 같이 밀려 나가고 세 칸이 서로 어긋났다. **바로잡은 방식**: 가로 스크롤 그룹을 둘로 나눴다 — 폭이 같은 것끼리만 묶는다. ① 위 원본 세 칸(3-Way) = 한 덩어리, ② MERGED(2-Way 에서는 BASE+LOCAL) = 한 덩어리. 원본 칸용으로 `SourceShiftX` 를 새로 두고 세 템플릿이 그 값만큼 **글자만** 민다(줄 번호·`[<]` 는 제자리). 2-Way 에서는 BASE 와 LOCAL 의 폭이 같으므로 `SourceShiftX` 가 `TextShiftX` 를 그대로 따라간다 — 지금까지의 동작 그대로다. 휠은 **마우스가 놓인 칸**을 민다(`WM_MOUSEHWHEEL` 의 좌표 대신 `Mouse.GetPosition` 을 쓴다 — DPI 가 섞인 화면에서 창 메시지의 픽셀 좌표를 직접 환산하면 어긋난다). 원본 칸의 제 스크롤 막대는 `Hidden` 으로 껐다 - 두 방식이 겹치면 같은 것을 두 번 미는 꼴이 된다. **실행 확인**(mergehub-demo, 3-Way = feature/cooling): BASE 위에서 오른쪽으로 6칸 → 세 칸이 **똑같이** 밀리고 줄 번호는 제자리, MERGED 는 그대로. 왼쪽으로 10칸 → 처음 자리에서 정확히 멈춤(과도 스크롤 없음). 2-Way 재확인: 100%→39.5%, BASE 동반 이동. |
| 2026-09-07 | **3-Way 확인 중 결함 둘 더 잡음 (원본 칸이 비어 버림 · 워드랩이 안 접힘)** | ① **MERGED 에 글자를 하나만 쳐도 LOCAL(Mine)·REMOTE 칸이 통째로 비었다.** 사용자는 "가로 휠을 하면 글이 사라진다"고 보았지만, 실제 원인은 휠이 아니라 **편집 후 재정렬**이었다 - `ReloadLinesFrom` 이 3-Way 여부와 무관하게 늘 `BuildLines`(2-Way)로 다시 만들고 있었다. 칸(레이아웃)은 `IsThreeWay` 를 따라 그대로 남고 내용만 빠지니 "글자가 사라진" 것처럼 보인다. REMOTE 전문을 `_remoteTextSnapshot` 으로 붙들어 두고, 3-Way 면 `BuildThreeWayLines` 로 다시 맞추게 고쳤다. **실행 확인**: MERGED 에 `abc` 한 번, 150자짜리 긴 줄 한 번 - 두 경우 모두 세 칸의 내용·Conflict 3·Theirs only 1 이 그대로 남는다. ② **워드랩을 켜도 원본 세 칸이 접히지 않았다.** 가로 이동량을 재려고 그 칸들의 가로 스크롤을 `Hidden`(막대만 감춤, 굴리기는 가능)으로 둔 것이 원인 - 굴릴 수 있으면 WPF 는 칸 너비를 무한으로 잡아 재므로 접힐 자리가 없다. 워드랩을 켜면 `Disabled`(너비 고정), 끄면 `Hidden`(폭 측정 가능)로 바꾸는 `SourceBars` 를 두어 해결. **실행 확인**: 워드랩 켠 3-Way 에서 LOCAL(Mine) 07행이 4줄, REMOTE 05행이 3줄로 접히고 BASE 쪽 빈자리 높이가 같이 늘어 **세 칸의 같은 행이 같은 눈높이**에 온다(09/05/06행 · 11/07/08행 · 19/15/16행 확인). 덤으로 표시층 ListBox 가 **스스로 굴러가지 못하게** 막았다(`BlockSelfScroll`) - ListBox 가 "고른 줄을 보이게" 하려고 제멋대로 굴리면 여백까지 밀려 나간다. 미니맵 이동도 편집기 캐럿 기준으로 바꿨다(표시층을 직접 굴리지 않는다). |
| 2026-09-07 | **남은 것 2가지 (확인됨, 미수정)** | ① **3-Way + 워드랩에서 위 세 칸과 아래 MERGED 의 세로 위치가 어긋난다.** 두 무리의 칸 너비가 달라 접히는 자리가 다르고, 따라서 같은 행이라도 높이가 다르다. 지금은 픽셀 오프셋으로 맞추므로 접힌 줄을 지날 때마다 조금씩 벌어진다(4칸 굴린 뒤 2행 차이). 제대로 맞추려면 픽셀이 아니라 **행 번호로** 맞춰야 하고, 그러려면 양쪽 모두 "N행의 픽셀 위치"를 되돌려 주는 길이 필요하다 - 스크롤 동기화를 다시 짜는 일이라 따로 잡는 편이 안전하다. 워드랩을 끄면 모든 칸의 행 높이가 같아 어긋나지 않는다. ② **MERGED 를 직접 편집하면 LOCAL(Mine) 칸도 그 편집을 따라간다.** MERGED 는 Mine 의 복사본에서 출발하고, 편집 후 재정렬은 "지금 글"로 다시 맞추기 때문이다. "파일을 연 시점의 내 줄"로 얼어붙게 하려면 편집 전 LOCAL 을 따로 정렬해 넣어야 하는데, 사용자가 줄을 더하거나 지우면 행이 어긋나 4-way 정렬이 된다. 반영(Accept·Take Mine/Theirs) 으로 고치는 경우에는 지금도 Mine 칸이 그대로 남는다. |
| 2026-09-07 | **남은 것 2가지 — 둘 다 완료** | ① **3-Way + 워드랩 세로 어긋남.** 위 원본 세 칸과 아래 MERGED 는 칸 너비가 달라 접히는 자리가 다르고, 따라서 같은 행이라도 높이가 다르다. 지금까지 픽셀 오프셋을 그대로 넘겨 맞추던 것을 **행 기준 변환**으로 바꿨다 — 편집기의 세로 위치 → 그 자리의 논리 행 + 행 안으로 들어간 비율 → 원본 칸의 행 높이표(`MergeLine.SourceRowHeight`)로 다시 픽셀. 반대 방향(원본 칸을 굴렸을 때)도 같은 식의 역변환을 넣었다. 변환은 워드랩을 켠 3-Way 에서만 탄다(`SourceRowsDifferFromEditor`) - 그 밖에는 모든 칸의 행 높이가 같아 픽셀 그대로가 정확하다. **실행 확인**: 워드랩 켠 3-Way 에서 아래로 굴리면 네 칸이 모두 05행에서 시작하고, BASE 칸에서 굴려도(역방향) 마찬가지다. 고치기 전에는 MERGED 10행 ↔ 원본 08행으로 벌어졌다. ② **MERGED 를 고치면 LOCAL(Mine) 칸도 따라가던 것.** 편집 후 재정렬이 "지금 글"을 MINE 자리에도 그대로 쓰고 있었다. **BASE 를 축으로 MINE·REMOTE·MERGED 세 쪽을 세우는 `ThreeWayAligner.AlignFour` 를 새로 만들어** 두 열을 갈랐다(`MergeLine.SetThreeWay` 로 MINE 을 따로 받는다). 방식은 기존 3-Way 정렬 그대로다 - BASE 와 2-way 정렬을 세 번 한 뒤 BASE 줄 번호로 맞물리고, BASE 줄이 없는 추가분은 **MINE↔MERGED 를 먼저 짝지은 뒤** 그 묶음을 REMOTE 와 짝짓는다(결과는 내 것의 복사본에서 출발하므로 이 순서라야 "내가 넣은 줄"이 편집 뒤에도 제자리에 남는다). **MERGED == MINE 이면 결과가 기존 3-Way 정렬과 글자 하나 다르지 않다** - 파일을 연 직후 화면은 그대로라는 뜻이고, 테스트로도 못 박았다. `ThreeWayTest` 20건 → **31건**(4-Way 11건 추가: 3-Way 와 동일함 2건, 결과만 고침·줄 추가·줄 삭제·내가 넣은 줄·저쪽 줄 가져오기·양쪽 추가·번호 갈라짐·빈 글 2건). **실행 확인**: MERGED 1행에 `abc` 를 쳐도 LOCAL(Mine) 1행은 `using System;` 그대로이고, 그 행에 Take Mine(`<`) 버튼이 생긴다. 네 칸의 행 정렬도 그대로다. 전체 **290건 통과**. |