Ubisam-MergeTool/doc/작업지시-사용성보강.md
ksj09406 7719094edf 최초 커밋: MergeHub (사내 Git Merge/Diff 도구)
UbisamBase 플랫폼 위에서 도는 플러그인. 지금까지 만든 것을 그대로 담는다.
- 폴더 비교 / 파일 비교(부분 수용·워드랩·찾기) / 3-Way 병합 / Git 조작
- 비교 엔진: histogram 정렬 · 이동 블록 감지 · 무시 규칙 · 13개 언어 토크나이저
- doc/ 에 작업 지시서와 진행 기록

빌드 산출물(bin/obj)은 추적하지 않는다 - 플랫폼 런타임이 빌드 때마다
D:\UbisamPlatform\bin 에서 복사되어 들어오므로 저장소에 담을 이유가 없다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:13:27 +09:00

177 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 작업 지시 — MergeHub 사용성 보강 (2차)
대상: `D:\Sorce\MergeTool\MergeHub` (플러그인) · `D:\Sorce\MergeTool\UbisamPlatform` (셸)
전제: 1차 지시서(`작업지시-머지툴보강.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 는 칸 너비를 <b>무한</b>으로 잡아 재므로 접힐 자리가 없다. 워드랩을 켜면 `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 의 세로 위치가 어긋난다.** 두 무리의 칸 너비가 달라 접히는 자리가 다르고, 따라서 같은 행이라도 높이가 다르다. 지금은 <b>픽셀 오프셋</b>으로 맞추므로 접힌 줄을 지날 때마다 조금씩 벌어진다(4칸 굴린 뒤 2행 차이). 제대로 맞추려면 픽셀이 아니라 **행 번호로** 맞춰야 하고, 그러려면 양쪽 모두 "N행의 픽셀 위치"를 되돌려 주는 길이 필요하다 - 스크롤 동기화를 다시 짜는 일이라 따로 잡는 편이 안전하다. 워드랩을 끄면 모든 칸의 행 높이가 같아 어긋나지 않는다. ② **MERGED 를 직접 편집하면 LOCAL(Mine) 칸도 그 편집을 따라간다.** MERGED 는 Mine 의 복사본에서 출발하고, 편집 후 재정렬은 "지금 글"로 다시 맞추기 때문이다. "파일을 연 시점의 내 줄"로 얼어붙게 하려면 편집 전 LOCAL 을 따로 정렬해 넣어야 하는데, 사용자가 줄을 더하거나 지우면 행이 어긋나 4-way 정렬이 된다. 반영(Accept·Take Mine/Theirs) 으로 고치는 경우에는 지금도 Mine 칸이 그대로 남는다. |
| 2026-09-07 | **남은 것 2가지 — 둘 다 완료** | ① **3-Way + 워드랩 세로 어긋남.** 위 원본 세 칸과 아래 MERGED 는 칸 너비가 달라 접히는 자리가 다르고, 따라서 <b>같은 행이라도 높이가 다르다.</b> 지금까지 픽셀 오프셋을 그대로 넘겨 맞추던 것을 **행 기준 변환**으로 바꿨다 — 편집기의 세로 위치 → 그 자리의 논리 행 + 행 안으로 들어간 비율 → 원본 칸의 행 높이표(`MergeLine.SourceRowHeight`)로 다시 픽셀. 반대 방향(원본 칸을 굴렸을 때)도 같은 식의 역변환을 넣었다. 변환은 <b>워드랩을 켠 3-Way 에서만</b> 탄다(`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건 통과**. |