Ubisam-MergeTool/doc/history/2026-09-18-11차.md
ksj09406 bc97a2d2e5 11차 F-4 기록: 플랫폼 배포함
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:23:30 +09:00

726 lines
82 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.

# 11차 진행 기록 (2026-09-18 ~)
지시서: [../작업지시/11차.md](../작업지시/11차.md)
## A-1. 변경분 추출을 먼저 뽑아 보고 적는다 (2026-09-18, 완료 · 고치지 않음)
지시서대로 **고치지 않고 적기만 했다.** 제품 코드는 바꾸지 않았다.
### 무엇으로 했나
- `C1Repo` 로 저장소를 새로 만든 뒤 `e11-a1-setup.sh` 로 추출 시험용 커밋을 더 쌓았다(전부 지어낸 파일).
| 커밋 | 무엇 |
|---|---|
| `4eb9ece` base | 내 쪽 작업 (C1Repo 의 feature/mine) |
| `d3eb15b` c1 | 새 파일 `Extract/Temp.cs` · `Io/Unit12.cs` 한 줄 더함 |
| `f884ed1` c2 | `Recipe/Unit11.cs` 지움 · `Motion/Unit16.cs` → `Motion/Renamed16.cs`(내용 그대로) · 이진 `Extract/logo.bin` · `한글폴더/깊은/경로/아래/설명.cs` |
| `cbcad8a` c3 | `Io/Unit12.cs` 를 c1 전으로 되돌림 · `Extract/Temp.cs` 지움 (범위 안에서 생겼다 사라짐) |
| `f702996` side · `58d5f7f` merge | base 에서 갈라진 옆가지에 `Side/SideOnly.cs`, 그것을 `--no-ff` 로 병합 |
- 화면은 사람 순서로: Git 탭 → 이력에서 줄을 **마우스로** 고르고 [여기부터] · [해제] · [변경분 추출], 폴더 창에는 경로를 쳐 넣고 Enter.
- 뽑은 폴더는 `git diff -M --name-status` 의 기대 목록과 견주고, 파일마다 `git hash-object --no-filters` 로 **바이트(blob 해시)** 를 대조했다.
### 아홉 경우의 결과
| # | 경우 | 알림 | 뽑힌 것 | git 과 견주면 |
|---|---|---|---|---|
| 1 | 커밋 하나(시작점 없음, c1) | "2개 파일 추출" | `Extract/Temp.cs` · `Io/Unit12.cs` | **같음** · 바이트 같음 |
| 2 | 범위 [여기부터] base → c3 | "3개 파일 추출 (삭제되어 건너뜀 1개)" | `Extract/logo.bin` · `Motion/Renamed16.cs` · `한글폴더/…/설명.cs` | **같음** — 범위 안에서 생겼다 사라진 `Temp.cs`, 바뀌었다 되돌아온 `Unit12.cs` 는 **들어가지 않는다**(맞다) |
| 3 | 지운 파일이 있는 범위 | 위와 같은 알림 | — | 건너뛴 **개수만** 알린다. `Recipe/Unit11.cs` 라는 것은 **화면 · 로그 · 폴더 어디에도 없다** |
| 4 | 이름 바뀐 파일 | — | `Motion/Renamed16.cs` (새 이름) | 새 경로로 바이트 그대로. **옛 경로(`Motion/Unit16.cs`)를 알 길이 없다** — 장비에 덮어 넣으면 옛 파일이 남는다 |
| 5 | 병합 커밋 하나(merge) | "1개 파일 추출" | `Side/SideOnly.cs` | **첫 부모(c3)와만** 견준다. 둘째 부모(옆가지)와 견주면 4개인데, 지금 무엇과 견주는지는 화면에 없다 |
| 6 | 거꾸로 범위 [여기부터] c3 → base | "2개 파일 추출 (삭제되어 건너뜀 2개)" | `Motion/Unit16.cs` · `Recipe/Unit11.cs` | git 의 `diff c3 base` 와 **같음**(되돌리는 방향으로 동작). **경고나 방향 표시는 없다** |
| 7 | 이진 · 한글 · 깊은 경로 | 2 와 같음 | `logo.bin` 278B · `설명.cs` 50B | **바이트 · 경로 그대로** |
| 8 | 이미 파일이 있는 폴더 | "3개 파일 추출 (삭제되어 건너뜀 1개)" | 폴더 안 **6개** | 묻지 않고 덮어쓴다. 앞서 뽑아 둔 `Extract/Temp.cs` · `Io/Unit12.cs`(c1 것)와 사람이 넣어 둔 `사용자메모.txt` 가 **그대로 남아 섞인다** |
| 9-a | 대상 파일이 읽기 전용 | **"변경분 추출 실패: … 액세스가 거부되었습니다"** | 2개(logo.bin + 읽기 전용 그대로) | **반쯤 뽑힌 채 멈춘다** — 앞 순서 `logo.bin` 은 써졌고 뒤 순서 `한글…설명.cs` 는 빠졌다. 몇 개까지 됐는지는 알림에 없다 |
| 9-b | 대상 파일이 다른 프로그램에 열려 있음 | **"… 다른 프로세스에서 사용 중이므로 …"** | 2개 | 9-a 와 같은 모양 |
- 알림과 같은 글이 Git 탭 로그에도 남는다 — 성공은 `변경분 추출: N개 파일 → 경로 (삭제되어 건너뜀 M개)`, 실패는 `[변경분 추출] 실패: …`.
- 범위 표시는 제대로 바뀐다: 시작점이 없으면 `부모 → 고른 커밋`, [여기부터] 를 누르면 `4eb9ece → 고른 커밋`.
- 폴더 창은 늘 **저장소가 있는 폴더**에서 열린다.
- 저장소 워킹트리는 아홉 경우 내내 Clean 이었다(추출은 읽기만 한다).
### 이걸로 확정된 것 (A-2 ~ A-4 로 넘긴다)
| 번호 | 확정된 문제 | 어디로 |
|---|---|---|
| ① | 지운 파일 · 이름 바뀐 옛 경로가 결과에 남지 않는다(경우 3 · 4) | **A-2** |
| ② | 무엇과 무엇 사이를 뽑았는지 폴더에 남지 않는다(경우 5 · 6 — 병합 커밋은 첫 부모, 거꾸로 범위도 말없이 됨) | **A-2** |
| ③ | 비어 있지 않은 폴더에 묻지 않고 덮어쓰고 옛 파일이 섞인다(경우 8) | **A-3** |
| ④ | 쓰다 막히면 반쯤 뽑힌 채 멈추고, 어디까지 됐는지 알 수 없다(경우 9-a · 9-b) | **A-3 에 덧** — 먼저 쓸 수 있는지 보고 시작할지, 실패해도 나머지를 계속할지 정한다 |
**맞게 도는 것**(고칠 것 없음): 파일 고르기(1 · 2 · 5 · 6 모두 git 과 같은 목록) · 바이트 보존(이진 · 한글 · 깊은 경로 포함) · 범위 표시 · 로그.
### 남긴 것 (시험 도구 쪽, 제품 아님)
화면 자동 조작에서 세 번 막혔다. 모두 스크립트 문제였고 제품과 무관하다.
- 함수 이름 `History` 가 PowerShell 기본 별칭(`history` = `Get-History`)과 겹쳐, 내 함수 대신 그것이 불렸다 → `GoHistory` 로 바꿈.
- 폴더 창(`#32770`)은 데스크톱 바로 아래가 아니라 **본 창 아래**에 붙는다 → 본 창의 자손에서 찾게 고침.
- 폴더 창의 경로 칸(`id 1152`)과 [이 폴더 선택](`id 1`)은 UIA 에서 **Pane** 으로 보이고, 같은 id 를 가진 다른 칸이 먼저 잡혔다 → id + 클래스(`Edit` · `Button`)로 고르고, 경로를 한 글자씩 친 뒤 Enter 로 닫게 함.
- 백그라운드로 돌리면 앱 창이 앞으로 나오지 못해 마우스가 다른 창을 누른다 → **앞에서** 돌린다.
## A-2. 뽑은 폴더 옆에 "무엇을 뽑았는지" 안내를 남긴다 (2026-09-18, 완료)
사용자 결정(2026-09-18): 안내는 **뽑은 폴더 밖(옆)** 에 두고, 장비에서 지울 파일은 **목록으로만** 적는다(지우는 배치 파일은 만들지 않는다).
### 왜
A-1 에서 값으로 확인한 것 — 지운 파일은 "삭제되어 건너뜀 1개" 라고 개수만 알리고 **어느 파일인지 아무 데도 없었다**. 이름이 바뀐 파일은 새 이름만 뽑혀, 그 폴더를 장비에 덮어 넣으면 **옛 파일이 그대로 남는다.** 무엇과 무엇 사이를 뽑았는지도 폴더만 보고는 알 수 없었다.
### 고친 것
| 자리 | 한 일 |
|---|---|
| `Services/ExportReport.cs` (새 파일) | 안내 글을 만든다. 머리글(만든 때 · 만든 사람 · 저장소 · 뽑은 곳 · 범위 두 줄) · **뽑은 파일 목록**(추가 · 수정 · 이름 바뀜은 옛 이름을 괄호로) · **장비에서 지워야 할 파일 목록**(지워진 파일 · 이름 바뀐 옛 경로). `PathFor` 가 폴더 옆 이름을 정한다 — `…\변경분` → `…\변경분_변경분안내.txt`, 드라이브 뿌리처럼 옆자리가 없으면 폴더 안에 |
| `Services/GitService.cs` | `ExportChangedFiles` 가 `(뽑은 수, 건너뛴 수, **무엇을 어떻게 했는지 목록**)` 을 돌려준다. git 의 상태 글자(Added · Renamed · Copied …)를 안내 글의 말로 옮기고, 이름이 바뀐 파일은 옛 경로를 함께 들고 온다 |
| `Services/IGitService.cs` | 같은 서명으로 |
| `ViewModels/GitViewModel.cs` | 뽑은 뒤 안내를 폴더 옆에 쓴다(`TextFile.Write`). 범위 줄은 이력에 있는 커밋이면 **제목과 날짜까지**, 시작점을 안 잡았으면 "(고른 커밋의 부모)" 를 붙인다. 안내 파일 경로를 로그에 남기고, 알림도 "지워야 할 파일 N개는 안내에" 로 바꿈. **안내를 못 써도 추출 자체는 실패로 만들지 않는다**(파일은 이미 다 뽑힌 뒤다) |
| 하네스 `ReportTest` | **+14건(31 → 45)** — 머리글 · 범위 두 줄 · 요약 수 · 이름 바뀜 표기 · 지울 목록 · 지운 파일이 뽑은 목록에 없음 · 한글 경로 · 빈 목록 · 만든 사람 없음 · 폴더 옆 이름 세 가지 |
### 확인한 것 — 화면에서 뽑아 git 과 대조
| 경우 | 안내에 적힌 것 | git `diff -M --name-status` |
|---|---|---|
| 범위 `4eb9ece` → `cbcad8a` | 뽑은 3개(추가 `Extract/logo.bin` · **이름 바뀜** `Motion/Renamed16.cs (옛 이름 Motion/Unit16.cs)` · 추가 `한글폴더/깊은/경로/아래/설명.cs`) · **지울 2개**(옛 이름 `Motion/Unit16.cs` · 지워짐 `Recipe/Unit11.cs`) | `A logo.bin` · `R100 Unit16.cs → Renamed16.cs` · `D Unit11.cs` · `A 설명.cs` — **같음** |
| 병합 커밋(시작점 없음) | 범위 `cbcad8a … (고른 커밋의 부모)` → `58d5f7f …` · 뽑은 1개 `Side/SideOnly.cs` · 지울 0개 | 같음 |
- 안내 파일은 폴더 **옆**에 생긴다(`note2b_변경분안내.txt` · `note5_변경분안내.txt`). 폴더 안에는 뽑은 파일만 있다.
- 알림: "3개 파일 추출 (지워야 할 파일 1개는 안내에)" · "1개 파일 추출 · 안내 note5_변경분안내.txt". Git 탭 로그에 안내 경로도 남는다.
- 하네스 22벌 **1,173건** 통과(1,159 → +14) · 빌드 경고 0 · 오류 0.
### 남긴 것 (시험 도구 쪽)
- 화면 잠금이 걸린 동안에는 마우스가 데스크톱에 닿지 않는다(커서 좌표가 0,0 으로 돌아가고 화면 캡처가 실패한다). 잠긴 줄 모르고 "클릭이 안 먹는다" 로 두 번 헤맸다 — 앞으로는 커서 자리와 맨 앞 창부터 본다.
- 목록에서 스크롤이 필요한 줄은 **누를 때마다 자리를 다시 읽어야** 한다. 옛 자리로 누르면 조용히 빗나가고, 그 뒤 [여기부터] 가 엉뚱한 커밋(HEAD)으로 잡혀 범위가 거꾸로 됐다.
- PowerShell 변수는 대소문자를 안 가린다 — `$S`(경로)와 `$s`(파일 글)가 겹쳐 경로가 깨졌다(9차 A-2 와 같은 종류).
## A-3. 이미 파일이 있는 폴더 · 쓰다 막힐 때 (2026-09-18, 완료)
사용자 결정(2026-09-18): **고른 폴더 아래 새 폴더를 만든다** · 쓰다 막히면 **먼저 보고 시작한다**(하나라도 못 쓰면 아무것도 쓰지 않는다).
### 고친 것
| 자리 | 한 일 |
|---|---|
| `Services/ExportPlan.cs` (새 파일) | `FolderName` — `변경분_4eb9ece..cbcad8a_2026-09-18` 처럼 범위와 날짜가 들어간 이름. `UniquePath` — 같은 이름이 있으면 `_2` · `_3`. `Blocked` — 쓰기 전에 읽기 전용 · 다른 프로그램이 잡고 있는 파일을 걸러 목록으로. `BlockedMessage` — 사람에게 보일 글(앞 다섯 개 + "외 N개") |
| `Services/GitService.cs` | 뽑기 전에 `ExportPlan.Blocked` 로 **먼저 본다**. 하나라도 막히면 폴더도 만들지 않고 `IOException` 으로 멈춘다 — 예전에는 앞 순서만 써진 반쯤 뽑힌 폴더가 남았다(A-1 경우 9) |
| `ViewModels/GitViewModel.cs` | 고른 폴더에 바로 쏟지 않고 **그 아래 새 폴더**를 만들어 거기에 뽑는다. 안내 글은 그 폴더 옆(= 고른 폴더 안)에 붙는다. 알림도 폴더 이름을 앞에 — "변경분_4eb9ece..cbcad8a_2026-09-18 에 3개 (지워야 할 파일 1개는 안내에)" |
| 하네스 `ReportTest` | **+12건(45 → 57)** — 폴더 이름 · `_2` · `_3` · 읽기 전용은 막힘 · 아직 없는 파일은 막힘 아님 · 열려 있는 파일은 막힘 · 보일 글의 개수와 줄임 |
### 확인한 것 — 화면에서
이미 파일이 있는 폴더(사람이 넣어 둔 `사용자메모.txt` · 옛 폴더 `옛자료`)를 골라 같은 범위를 **두 번** 뽑았다.
| 때 | 결과 |
|---|---|
| 첫 번째 | `…\a3\변경분_4eb9ece..cbcad8a_2026-09-18` 에 3개 · 안내는 그 옆에 |
| 두 번째 | `…_2` 폴더가 새로 생기고 거기에 3개 · 안내도 `…_2_변경분안내.txt` |
| 고른 폴더 안 | 새 폴더 둘 · 안내 둘 · **사용자메모.txt 와 옛자료 그대로** — 섞이지 않았다 |
- **먼저 보기**(`Blocked`)는 하네스 12건으로 확인했다. 화면에서는 재현되지 않는데, 뽑을 때마다 **비어 있는 새 폴더**를 만들기 때문이다 — A-1 경우 9(반쯤 뽑힌 폴더)가 애초에 생기지 않는다. 남겨 둔 까닭은 폴더가 이미 있는 드문 경우(다른 프로그램이 같은 이름을 방금 만든 경우)와, 나중에 폴더를 다시 쓰게 고칠 때를 위한 것이다.
- 하네스 22벌 **1,185건** 통과(1,173 → +12) · 빌드 경고 0 · 오류 0.
### 남은 것
- A-4(ZIP)는 아래(09-22).
- 매뉴얼 · 웹 문서의 변경분 추출 설명(새 폴더 · 안내 파일)은 E-2 에서 함께 고쳤다.
## A-4. 변경분을 ZIP 으로도 (2026-09-22, 완료)
사용자 결정: **폴더에 더해 ZIP 도**(권장안). 장비에는 폴더를 넣고, 메일 · 메신저로 보낼 때 ZIP 을 쓴다.
### 만든 것
| 자리 | 한 일 |
|---|---|
| `Services/ExportZip.cs` (새) | `PathFor` — 뽑은 폴더 옆, 폴더 이름 + `.zip`. `Create` — 안에 **뽑은 폴더와 안내 파일을 나란히** 넣는다(풀면 폴더로 뽑은 것과 같은 모양, 안내가 소스 틈에 섞이지 않는 A-2 결정 그대로). 파일은 바이트 그대로 · 경로는 `/` · 수정 시각 그대로. `.part` 이름에 쓰고 다 되면 옮겨, 쓰다 막혀도 반쯤 된 ZIP 이 남지 않는다 |
| `Services/ExportPlan.cs` | `UniquePath` 가 같은 이름의 `.zip` 이 남아 있어도 그 이름을 쓴 것으로 본다 — 폴더만 지우고 ZIP 을 남긴 채 다시 뽑으면 옛 ZIP 을 덮을 뻔했다 |
| `ViewModels/GitViewModel.cs` | 폴더 · 안내를 쓴 뒤 ZIP 을 만든다. 못 만들어도 폴더는 이미 다 뽑혔으므로 추출은 성공(안내와 같은 규칙), 로그에 까닭. 알림 끝에 "· ZIP 도" / "· ZIP 은 못 만듦(로그)" |
| `MergeHub.csproj` | `System.IO.Compression` 참조 — .NET Framework 에 들어 있어 배포 묶음 파일이 늘지 않는다(빌드 출력에 새 dll 없음) |
| 하네스 `ReportTest` | **+13건(57 → 70)** — ZIP 자리 · 넣은 수 · 풀면 같은 이름 폴더 · 경로 · 해시 목록이 폴더와 같음 · 안내는 폴더 밖 · 안내 바이트 · 빈 파일 · `/` 경로 · 한글 경로 · 한글 이름 UTF-8 표시(bit 11) · 같은 이름 ZIP 이 있으면 `_2`(폴더가 없어도). 고치기 전에 넣어 "폴더 없이 ZIP 만 남은 경우" 가 실패하는 것을 보고 시작 |
### 확인 — 화면에서 아홉 경우 다시 (`e11-a1-setup.sh` · `e11-e1-a.ps1` · `e11-a4-verify.sh`)
- 모든 경우 알림 끝에 **"· ZIP 도"**. 새 폴더 8개마다 옆에 ZIP.
- ZIP 을 앱과 **다른 도구**(Windows `tar.exe`)로 풀어 뽑은 폴더와 견줌 — **8/8 경로 · SHA-256 목록 같음**, 안내 파일 바이트 같음 · 폴더 밖.
- 앱(.NET Framework 4.8)이 만든 ZIP 의 이름 칸 — `변경분_…/한글폴더/깊은/경로/아래/설명.cs` 까지 모두 **UTF-8 표시(bit 11) 켜짐**. 하네스는 .NET 8 에서 돌므로 이 표시는 실제 앱이 만든 파일에서 따로 확인했다.
- 하네스 22벌 **1,260건**(1,247 → +13) · 빌드 경고 0 · 오류 0 · 설정 해시 같음.
### 문서
- 웹 문서(버전 14): 매뉴얼 "변경분 추출" 에 ZIP 한 단계, 남은 일 A-4 완료, "기획안에서 아직 안 한 것" 에서 ZIP 뺌, 기획안 v2 표 변경분 추출 완료, 숫자 1,260건 · 하네스 막대 폭 다시 잼.
- 기획안: 변경분 파일 추출 "일부" → **완료**, Git 작업 버튼 줄 완료, 1,260건. 8쪽 A4 높이 그대로. 원문(`plan/mergetool.html`, 비교 원본 `mergetool-0922.html`)만 고쳐 두었다.
- **웹 문서 · 기획안은 11차가 끝날 때 한 번에** 반영 · 잠금 · 발행한다(2026-09-22 사용자: "11차 끝내기 전에는 웹문서 업데이트하지마", "11차 끝나면 한번에 잠글게"). 버전 14 는 그 말 전에 올라간 것이라 그대로 둔다.
- ZIP 안이 텍스트로 바뀌는지 물음이 있어 앱이 만든 ZIP 두 개를 풀어 git 의 그 커밋과 파일마다 대조 — `.cs` · `.bin` 이 원래 이름 · 확장자 · 폴더 · BOM · 줄끝 그대로 **바이트 같음**. 텍스트는 폴더 옆 안내 파일 한 장뿐.
### 11차 끝에 웹 문서에 넣을 것 (모아 둠)
- 개요 "로컬 커밋" 숫자(버전 14 는 129).
- 이 뒤 11차 항목(F 등)에서 바뀌는 화면 · 설정 · 숫자.
- F-1: 칸 이름 `LOCAL(Mine)` · `REMOTE(Theirs)`, 칸 머리 "어느 쪽 · 어느 파일"(MERGED · 두 칸 LOCAL 은 "(저장할 파일)", mergetool 은 임시 파일 이름), 긴 이름은 가운데 줄임 · 툴팁. 머지 주석 모양 `[사람] 날짜 출처 설명`(출처 = 로그의 말 — Theirs(…) · Mine(…) · REMOTE(…) · BASE(…)), **Take Mine 도 주석**, 두 칸 로그 `BASE(해시) 반영` · 브랜치 3-Way 로그 `REMOTE(브랜치) 반영`. 매뉴얼의 주석 예시 · 칸 설명 · 해결 기록이 남는 곳 표. 숫자 1,312건.
- F-2: [이 메서드 반영](C# · C/C++, 클래스 멤버 통째, 단추 아래 한 줄에 잡은 범위 · 못 잡은 까닭), 덩어리 · 메서드 반영은 주석 한 줄 · 로그 한 줄 · Undo 한 번. 매뉴얼의 반영 단추 설명에. 숫자 23벌 1,395건.
- F-3: 매뉴얼 "탐색기에서 열기" — 여러 개를 골라 보내면 앞의 둘(3개 이상은 알림), 폴더 둘은 바로 폴더 비교, 260자 넘는 경로는 열지 못함(알림), 배포 묶음을 옮겨도 켜면 보내기 바로가기가 따라옴, 설치 파일 필요 없음. 알아 둘 한계에 "떠 있어도 새 창". 숫자 23벌 1,411건.
- F-4: 매뉴얼 파일 비교 화면 — [ACTIONS 감추기] · F9, 아래 탭 줄 [탭 접기] · 띠 · F11(플랫폼), 단축키 표에 F9 · F11. 숫자 23벌 1,414건 · 플랫폼 31건.
## B-1. 배포 묶음을 다시 만들고 꼭 필요한 파일을 가림 (2026-09-22, 완료)
### 고치기 전 — 매뉴얼이 가리키는 Release 는 09-07 것
| 무엇 | 값 |
|---|---|
| `bin\Release\net48` | 65개 · 96MB. `MergeHub.dll` 09-07 14:11, 셸 09-07 13:41 (플랫폼은 그 뒤 09-16 에 다시 배포됨) |
| 옛 Release 로 켜면 | 오류 없이 켜지지만 **본 탭이 3개**(지금은 4개 — 폴더 비교 탭이 없음). 지금 소스의 형식 142개 가운데 **74개가 옛 dll 에 없다** — 폴더 비교(`FolderView` · `FolderComparer`), 3분할(`ThreeWayAligner`), 병합 저장 판단(`MergeSavePlan`), 단축키(`ShortcutMap`), 변경분 안내(`ExportReport` · `ExportPlan`), 커밋 수정 판단(`CommitSelection` · `CommitReword`) 등 6차 이후 것 (`e11-b1-old.ps1`, 이름 대조) |
### 무엇이 필요한지 가린 길
1. **참조로** — 셸 · MergeHub 에서 시작해 dll 이 부르는 dll 을 따라갔다. 어디서도 닿지 않는 것 7개: `GLWpfControl` · `OpenTK` · `SkiaSharp.Views.WPF` · `SkiaSharp.Views.Desktop.Common` · `LiveChartsCore.SkiaSharpView.WPF` · `Microsoft.Extensions.Options` · `…Options.ConfigurationExtensions`. 플랫폼 dll 안에 이 이름이 글자로도 없다(XAML 로 부르는 것도 아님).
2. **플랫폼이 쓰는 것** — `UbisamBase.Core` 가 `SkiaSharp` · `LiveChartsCore` · `LiveChartsCore.SkiaSharpView` 를 **직접 참조**한다(`Charting/ChartTheme.cs`, 테마를 바꿀 때 `UiSettingsService` 가 부름). MergeHub 는 안 쓰지만 **플랫폼 몫이라 남긴다**(사용자 결정 2026-09-22 — 빼면 약 8MB 가 되지만 플랫폼이 바뀔 때마다 다시 재야 한다).
3. **셸이 켜질 때 하는 일** — 셸은 폴더의 `*.dll` 을 모두 `Assembly.LoadFrom` 으로 열어 `IAppSetup` 을 찾는다(`AppSetupLoader.cs:16`). 쓰지 않는 dll 도 이름 순서상 `MergeHub.dll` 앞이면 켤 때마다 읽힌다 — 빼면 그만큼 덜 읽는다. `Lang` 폴더는 없으면 플랫폼이 만든다(`LanguageService.cs`), 지금은 비어 있다.
4. **다른 OS · CPU** — 맥용 dylib 3개(17.5MB), 리눅스 · 맥용 libgit2 9개(약 19MB), ARM 용(Skia · HarfBuzz 11MB, libgit2). 이 PC 에서 셸은 **64비트**로 뜬다. x86 은 32비트 Windows 를 위해 남긴다.
5. **지워 보고 켜서** — 통째 복사본(65개 · 97MB)과 줄인 묶음(41개 · 44MB)을 `%TEMP%` 에 따로 두고 켰다. 둘 다 오류 없이 켜지고 본 탭 4개가 모두 열렸다(UIA 로만 이동, `e11-b1-run.ps1`).
### 빈 폴더에서 병합 한 길 (`e11-b1-merge.ps1` — 10차 E-1 의 병합 길 그대로, 실행 파일만 묶음으로)
| 무엇 | 결과 |
|---|---|
| 실행 파일 | `%TEMP%\mergehub-beta\UbisamBase.Shell.exe` (줄인 묶음) |
| 병합 | `feature/theirs → feature/mine` 충돌 24건 (머지 주석만 다른 2건 자동) |
| 풀기 | 파일 단위 15 · 줄 단위 7 · 파일 지우기 · 남기기 · 누른 Take 18번 = 줄 고른 로그 18줄 |
| 병합 커밋 | `647f1f8` 부모 둘 · 본문 "충돌 해결 26개" · 줄끝 · BOM · 인코딩 바뀐 파일 0 · 충돌 표식 0 · 워킹트리 깨끗 |
| 두 탭 | Git 탭에서 고른 커밋을 파일 비교 탭이 따라감(10차 B-1) |
| 걸린 시간 | 304초 |
### 만든 것
| 자리 | 한 일 |
|---|---|
| `scripts\make-bundle.ps1` (새) | Release 빌드(vswhere 로 MSBuild 를 찾음, `-NoBuild` 로 건너뜀) → 뺄 것 24개를 **까닭과 함께 한 표**에서 빼고 → `-Out`(기본 `%TEMP%\mergehub-beta`)에 옮기고 → 꼭 있어야 할 것(셸 · Core · MergeHub · LibGit2Sharp · git2 x64)을 확인하고 → 묶음 안에 `배포묶음.txt`(파일 · 크기 · 까닭, 뺀 것 · 까닭, 만든 시각 · 커밋)를 쓴다. **다른 파일이 든 폴더는 비우지 않고 멈춘다**(`배포묶음.txt` 가 있는 폴더 · 빈 폴더만) — 남의 파일을 넣어 둔 폴더로 확인함 |
| 매뉴얼(`doc/web/index.html` 켜기와 탭 구성) | "Release 로 다시 빌드해야 최신" · 다른 PC 에는 스크립트로 묶음을 만들어 폴더째 복사 · `배포묶음.txt` 설명 |
- 스크립트로 만든 묶음이 손으로 만들어 시험한 묶음과 **파일 목록이 같고**, 다시 켜도 오류 없이 네 탭이 열렸다.
- Release 빌드 경고 0 · 오류 0. 제품 코드 변경 없음(스크립트 · 문서만).
### 배포 묶음 (41개 · 43.5MB)
| 묶음 | 파일 | 크기 | 까닭 |
|---|---|---|---|
| 셸 · 플랫폼 | `UbisamBase.Shell.exe` · `.exe.config` · `UbisamBase.Core.dll` | 0.7MB | 켜는 것 · .NET 4.8 · 버전 맞춤 · 플랫폼 |
| MergeHub | `MergeHub.dll` | 0.6MB | 셸이 폴더의 dll 을 훑어 찾는다 |
| Git | `LibGit2Sharp.dll` · `lib\win32\x64\git2-*.dll` · `lib\win32\x86\git2-*.dll` | 4.0MB | Git 조작 · 네이티브 |
| MVVM · 인코딩 | `CommunityToolkit.Mvvm.dll` · `System.Text.Encoding.CodePages.dll` | 0.8MB | 플랫폼 · MergeHub · CP949 판별 |
| 플랫폼 설정 · DI | `Microsoft.Extensions.*` 11개 | 0.5MB | 플랫폼 |
| .NET 보강 | `System.*` 9개 · `Microsoft.Bcl.AsyncInterfaces` | 1.1MB | 플랫폼 · Json |
| 차트(플랫폼 몫) | `LiveChartsCore` 둘 · `SkiaSharp` 둘 · `HarfBuzzSharp` · 네이티브 6개(루트 · x64 · x86) | 35.8MB | 플랫폼 Core 가 참조(`ChartTheme`) |
### 남은 것
- 묶음은 `%TEMP%` 에 있다. 다른 PC 에 줄 자리(공유 폴더 · 문서 폴더)는 베타 때 정한다.
- 지시서의 "다섯 탭" 은 지금 본 탭 4개(폴더 비교 · 파일 비교 · Git · 설정)다 — 지시서 글이 옛 것.
- 웹 문서 발행(아티팩트)은 E-2 에서.
- 플랫폼 배포 폴더(`D:\UbisamPlatform\bin`)는 **손대지 않았다**. 플랫폼 README 가 "UbisamBase.Shell 을 Release 로 빌드하면 이 폴더를 갱신" 한다고 하므로, 플랫폼을 새로 배포하면 이 스크립트를 다시 돌려야 한다.
## B-2. D 드라이브가 없는 PC · 설정이 없는 첫 실행 (2026-09-22, 완료 — 플랫폼에서 고치고 확인 뒤 배포)
지시서대로 **D 드라이브는 건드리지 않았다.** `D:\UbisamConfig` 는 먼저 통째로 백업하고(20개, 파일마다 SHA256 · 크기 · 수정 시각 목록) 이름만 잠시 바꿔 재고, 끝날 때마다 되돌려 목록과 대조했다(두 번 모두 같음). 시험은 모두 UIA 로만 누르고 읽었다(마우스 · 키보드 · 화면 찍기 없음).
### 설정이 없는 첫 실행 (배포 묶음 `%TEMP%\mergehub-beta`, `e11-b2.ps1`)
| 볼 것 | 결과 |
|---|---|
| 오류 창 | **없음** |
| 이름 창 | 빈 칸으로 뜬다 · 빈 채로 [저장] 하면 "이름을 입력하세요." 로 막힘 · 이름을 넣고 [저장] 하면 닫힘 |
| 켜는 순간 생기는 것 | `Settings\MergeHubSettings.json` · `Settings\UiSettingsService.json`. 이름을 저장하면 `CurrentUserService.json`, 첫 기록 때 `Log\` |
| 기본값 | `MergeHubSettings.json` 이 매뉴얼 설정 표와 **모두 같다** — 원격 이름 origin · 줄 필터 켬 · 단어 단위 · 문법 강조 켬 · 커밋 조회 200 · 머지 주석 남기기 · 무시 켬 · C# 스타일 1(AURORA, F-12) · 공백 단계 0 · 나머지 비움 |
| 창 위치 | 주 모니터에 **최대화**(2560×1600). 닫으면 `ShellWindowPlacement.json`(보통 크기 2048×1222 · Maximized true) |
| 네 탭 | 모두 열림, 오류 글 없음 |
### D 드라이브가 없을 때 — 코드로 따라가고, 같은 줄에서 실패하게 해 봄
- 셸 `App.OnStartup` → `UbisamAppBuilder.Run` 의 첫 줄 `UiSettingsService.Initialize(D:\UbisamConfig)` → `Directory.CreateDirectory(D:\UbisamConfig\Settings)`(`UiSettingsService.cs:86`)에 **감싼 곳이 없다**. 드라이브가 없으면 `DirectoryNotFoundException`. 로그 서비스는 그보다 **뒤에** 준비된다.
- 드라이브를 없애는 대신 `D:\UbisamConfig` 자리에 **같은 이름의 빈 파일**을 두면 같은 줄이 `IOException` 으로 실패한다.
- 결과: 셸의 공용 예외 창 **"문제가 생겼습니다 — 작업 중 문제가 생겨 이 동작을 멈췄습니다. 프로그램은 계속 쓸 수 있습니다 … 자세한 내용은 D:\UbisamConfig\Log 아래 로그 파일에 남았습니다."** 두 말 모두 사실이 아니다(본 창 없음 · 로그 없음). [확인] 을 누르면 **창 없는 프로세스가 30초 뒤에도 남았다**. 다시 켜면 하나씩 더 쌓인다.
### 정한 것 (2026-09-22 사용자) · 고친 것 — 플랫폼
- **D 를 못 쓰면 `%LOCALAPPDATA%\UbisamConfig`** 에 쓴다(권장안). **켜는 중 실패는 알리고 끝낸다**(권장안).
- 플랫폼 커밋 `377ffd3` — `Bootstrap/ConfigLocation`(새) · `Bootstrap/StartupMessages`(새) · `UbisamAppBuilder.ConfigRoot` 를 `const` → 속성(+ `ConfigFallbackReason`), 로그를 가장 먼저 준비 · `LockConfirmDialog` 의 ScreenSaver 자리 · `LogService.LogDirectory` · 셸 `App.OnStartup` 에서 켜는 중 실패를 받아 `Shutdown(1)`. 플랫폼 `changelog.md` 에 다른 소비 프로그램 영향과 함께 적음, `platform-guide.md` 11장 · 세 곳 고침.
- 하네스 `PlatformCfgTest`(새, 플랫폼 두 파일을 엮음) **24건** — 자리 고르기 · 둘 다 안 될 때 · 실제 임시 폴더 · 같은 이름 파일 · **없는 드라이브 글자**로 실제로 다른 자리를 고름 · 실패 글(켜는 중에는 "계속 쓸 수 있습니다" 를 안 함 · 로그가 없으면 "남았습니다" 를 안 함).
- 플랫폼은 **Debug 로만** 빌드했다(경고 0 · 오류 0). Release 는 빌드 후 단계가 곧바로 `D:\UbisamPlatform\bin` 을 덮어쓰기 때문이다 — 배포 폴더는 그대로(09-16).
### 고친 뒤 (배포 묶음에 고친 Debug Core · Shell 두 파일만 바꿔 넣은 `%TEMP%\mergehub-beta-b2`)
| 경우 | 결과 |
|---|---|
| D:\ 에 만들 수 있을 때 | 예전처럼 `D:\UbisamConfig` · `%LOCALAPPDATA%` 에 아무것도 안 생김 · 경고 없음 |
| D:\ 자리가 막혔을 때 | `C:\Users\Ubisam\AppData\Local\UbisamConfig` 에 Settings 넷 · Log 가 생기고 이름 창 → 네 탭 → 정상 종료. 로그 첫 줄 `[W] Platform: D:\UbisamConfig 를 쓸 수 없어 C:\Users\Ubisam\AppData\Local\UbisamConfig 에 설정 · 로그를 둡니다 (같은 이름을 가진 파일 또는 디렉터리가 이미 있어서 …)` |
| 두 자리 모두 막혔을 때 | 창 **"켜지 못했습니다"** — "프로그램을 켜지 못해 끝냅니다. 설정 폴더를 만들 수 없습니다. D:\UbisamConfig : … / C:\Users\Ubisam\AppData\Local\UbisamConfig : …" (로그가 없으니 로그 얘기는 없음) → [확인] 뒤 **프로세스 끝남** |
- MergeHub 는 이전 Core 로 빌드된 그대로 새 Core 위에서 돌았다(바뀐 것은 속성 추가뿐, MergeHub 는 `ConfigRoot` 를 읽지 않는다).
- MergeHub 쪽: 매뉴얼 "설정 — … MergeHubSettings.json 에 저장된다" 에 D 없는 PC 의 자리를 덧붙이고, `MergeHubSettings.cs` 주석을 사실대로. 하네스 22벌 1,247건 그대로 · 빌드 경고 0 · 오류 0.
### 배포 (2026-09-22 사용자 확인 뒤)
- `publish-platform.ps1` 로 `D:\UbisamPlatform\bin` 갱신(배포 시각 11:27:25, 경고 0 · 오류 0). 48개 가운데 바뀐 것은 **`UbisamBase.Core.dll` · `UbisamBase.Shell.exe` · `README.txt` 셋**, 없어진 파일 없음(배포 전 목록 · 해시와 대조).
- MergeHub 를 다시 빌드하고 `make-bundle.ps1` 로 묶음을 다시 만듦(41개 · 43.5MB). 묶음 · Debug 출력의 Core · Shell 이 배포 폴더와 해시가 같다. 새 묶음으로 켜 네 탭 오류 없음 · 로그에 자리 경고 없음(D 를 씀).
- 시험이 남긴 것(시험용 설정 폴더 셋 · 빈 파일 셋 · `%LOCALAPPDATA%` 쪽 폴더 · 시험 묶음 둘)은 사용자 말대로 모두 지움. `D:\UbisamConfig` 원본만 남음.
### 남은 것
- 드라이브는 있는데 **읽기 전용**(CD · 쓰기 막힌 USB)인 경우도 같은 길(`CreateDirectory` 실패 → 다른 자리)로 가지만 재 보지는 않았다. 폴더는 있는데 파일만 못 쓰는 경우(권한)는 이번에 다루지 않았다.
## C-1. 있는 [커밋 수정] 을 다시 내 보고 바로잡음 (2026-09-18, 완료 — 8 만 이번 범위 밖)
### 고치기 전에 여덟 가지를 화면에서 다시 냈다
시험 저장소: `C1Repo` 위에 `amend` 브랜치를 만들고 **작성자 박지훈인 커밋**을 HEAD 로, 다른 브랜치(`feature/revert`)에는 **더 새 커밋**을, 그리고 bare 저장소를 원격으로 걸어 upstream 을 두었다(`e11-c1-setup.sh`).
| # | 고치기 전에 본 것 |
|---|---|
| 1 | [모든 브랜치] 켠 채 [마지막 메시지] → 칸에 `딴 브랜치 최신 작업`(HEAD 아님). 그대로 [커밋 수정] 하니 **HEAD 제목이 그 메시지로 바뀌었다** |
| 2 | 앱에서 [전체 스테이징] 한 파일이 **말없이 커밋에 들어갔다**(남은 변경 없음). 알림은 "커밋 수정" 뿐 |
| 3 | 작성자 `박지훈` · 작성 `2026-09-15 10:00` → **`시험` · `2026-09-18 15:48`** 로 바뀜 |
| 4 | 충돌 24개가 남은 채로도 단추가 **켜져 있고**, 누르면 `[커밋 수정] 실패: cannot create a tree from a not fully merged index` |
| 5 | 병합 커밋을 고쳐도 **부모 둘 · 본문(충돌 해결 26개 · 29줄) 그대로** |
| 6 | 원격에 이미 올라간 커밋도 **막지 않고** 고쳐졌고, 그 뒤 [Push] 가 `cannot push non-fastforwardable reference` 로 거부됨 |
| 7 | 알림 · 로그에 **새 해시만**(`커밋: 61653f5 …`). 고치기 전 해시는 어디에도 없음 |
| 8 | 커밋 메시지 칸이 한 줄짜리라 **여러 줄 메시지를 아예 칠 수 없다**. `#` 로 시작하는 글자는 그대로 들어간다 |
### 고친 것 (1 · 3 · 4 · 7)
| 자리 | 한 일 |
|---|---|
| `Services/CommitSelection.cs` | `MessageOfHead` — [마지막 메시지] 는 **HEAD 커밋**의 메시지를 읽는다(목록 순서와 무관). HEAD 가 목록에 없으면 아무것도 주지 않는다. `CanAmend` — **작업 중이면 끈다**. `AmendHint` — 꺼진 까닭을 단추 설명으로. `AmendDone` — "커밋 수정 완료 (옛 abc1234 → 새 def5678) · 파일 N개 포함" |
| `Services/GitService.cs` | 고쳐 쓸 때 **원래 작성자 · 작성 시각을 그대로** 쓴다(git 과 같게). 커밋한 사람 · 시각만 지금 |
| `Services/GitWorkspace.cs` | `HeadFullSha` 를 들고 있는다 — 이력 목록의 Sha 와 맞춰 보려면 짧은 해시로는 모자란다 |
| `ViewModels/GitViewModel.cs` | 위 판단을 붙이고, 고치기 전 해시 · 함께 들어간 파일 수를 알림에. 작업이 시작 · 끝날 때 단추 설명도 다시 그린다 |
| `Views/GitView.xaml` | [커밋 수정] 설명을 상황에 따라 바뀌는 값으로 |
| 하네스 `MergeSaveTest` | **+15건(210 → 225)** — HEAD 메시지 읽기(맨 위가 딴 브랜치여도 · 대소문자 · HEAD 가 목록에 없음 · 빈 목록) · 켜는 조건 넷 · 꺼진 까닭 · 알림 글 셋 |
### 고친 뒤 같은 순서로 다시
| # | 결과 |
|---|---|
| 1 | 칸에 **`남이 만든 커밋`**(HEAD 것) |
| 3 | 고친 뒤에도 작성자 `박지훈` · 작성 `2026-09-15 10:00` **그대로**, 커밋한 사람 · 시각만 지금 |
| 4 | 충돌 24개가 남은 동안 단추 **꺼짐** — 누를 수 없고, 누른 뒤 실패 알림도 없다 |
| 7 | 알림 **`커밋 수정 완료 (옛 bcd2c43 → 새 fd7810c) · 파일 1개 포함`** |
- 하네스 22벌 **1,200건** 통과(1,185 → +15) · 빌드 경고 0 · 오류 0.
### 이어서 고친 것 (2 · 6) — "먼저 묻기" (2026-09-18 사용자 결정)
두 가지 모두 **막지 않고 먼저 묻는다**. 막으면 정말로 그러려는 사람(빠뜨린 파일을 같이 넣기 · 아직 남이 안 받은 커밋 고치기)까지 못 하게 되고, 그대로 두면 모르는 새 벌어진다.
| 자리 | 한 일 |
|---|---|
| `Services/CommitSelection.cs` | `AmendWarning(스테이징 수, 이미 올림, 원격 브랜치)` — 물을 것이 없으면 **null**(안 묻는다). 있으면 해당하는 줄만 모아 "… ¶ 계속할까요?" 한 글로 |
| `ViewModels/GitViewModel.cs` | `StagedFileCount`(충돌 뺀 스테이징 수) · `HeadTrackedBranch` · `IsHeadAlreadyPushed`(추적 브랜치가 있고 앞선 커밋 0개). 물을 것이 있으면 `MessageUtil.ShowYesNo` 로 먼저 묻고, [아니오] 면 **커밋도 메시지 칸도 그대로 두고 아무 일도 하지 않는다** |
| 하네스 `MergeSaveTest` | **+7건(225 → 232)** — 안 묻는 경우 · 스테이징만 · 올린 것만 · 둘 다 · 브랜치 이름을 모를 때 "원격" · "Push 가 거부됩니다" · 끝이 늘 "계속할까요?" |
앞선 커밋 수는 **마지막 fetch 시점** 기준이라, fetch 전에 남이 받아 간 것까지는 알 수 없다. 그래서 막지 않고 묻기만 한다(주석에 남김).
### 고친 뒤 화면에서 (`e11-c1g.ps1`)
스테이징 1개를 만들어 두고, HEAD 가 `origin/amend` 와 같은 자리인 채로 냈다 — **두 가지가 겹치는 자리**다.
| 무엇 | 결과 |
|---|---|
| [커밋 수정] 누름 | 확인 창 `커밋 수정` — "스테이징해 둔 파일 1개도 이 커밋에 들어갑니다. / 이 커밋은 origin/amend 에 이미 올라가 있습니다. 고치면 다음 Push 가 거부됩니다. / 계속할까요?" · 단추 [아니오] [예] (`e11-c1g-ask.png`) |
| [아니오] | HEAD `62dd57f` **그대로** · 스테이징 `Recipe/Unit19.cs` **그대로** · 메시지 칸 `묻는지 확인` **그대로** · 창 닫힘 |
| 다시 누르고 [예] | HEAD `62dd57f` → `6207b26` · 알림 `커밋 수정 완료 (옛 62dd57f → 새 6207b26) · 파일 1개 포함` |
| 스테이징 없음 · 원격보다 앞선 자리에서 다시 | **안 묻고 바로** 고침 (`ceaab5f`, 작성자 `박지훈` 그대로) (`e11-c1g-done.png`) |
- 하네스 22벌 **1,207건** 통과(1,200 → +7) · 빌드 경고 0 · 오류 0 · 설정 파일 해시 같음.
### 남은 것
- **8. 여러 줄 커밋 메시지** — 칸이 한 줄이라 본문을 칠 수 없다. 이번 범위 밖으로 적어 둔다(병합 커밋 본문은 앱이 저절로 붙인다). C-2 의 [메시지 바꾸기] 창은 여러 줄을 받는다.
- C-2 는 아래(2026-09-22 완료).
## C-2. 마지막이 아닌 커밋의 메시지 바꾸기 (2026-09-18 ~ 09-22, 완료 — 좁은 범위)
사용자 결정(2026-09-18): **한다 — 좁은 범위로.** 화면은 기획안 문구대로 **이력 줄 우클릭 메뉴**.
### 고치기 전
- 이력 줄을 우클릭해도 **메뉴가 없고**, 이력 탭 단추는 Revert · Cherry-pick · Reset · 여기부터 · 해제 · 변경분 추출뿐이었다(`e11-c2-before.ps1`). 고른 커밋의 메시지를 바꿀 길이 없었다.
- 메시지는 커밋 해시에 들어가므로, HEAD 가 아닌 커밋을 바꾸면 **그 커밋부터 HEAD 까지가 새 해시로 다시 써진다**. 아직 안 올린 커밋이면 브랜치가 새 줄기로 옮겨 갈 뿐이지만, 올린 뒤라면 남의 이력과 갈라진다(사용자에게 설명함).
### 만든 것
| 자리 | 한 일 |
|---|---|
| `Services/CommitReword.cs` (새) | 막을 까닭 — 고른 커밋 없음 · 작업 중 · 커밋 안 한 변경 · 지금 브랜치 조상 아님 · 원격에 올라감 · 다른 로컬 브랜치도 품음 · 구간에 태그 · 사이에 병합 커밋. 먼저 풀어야 할 것부터 말하고 **푸는 길**까지 적는다. 확인 글 · 완료 알림 · 메뉴 설명. `Clean` — 새 메시지를 git `-m` 처럼 다듬는다(아래) |
| `Services/GitService.cs` · `IGitService.cs` · `Models/GitModels.cs` | `InspectReword` — 저장소에서 사실만 읽는다(다시 쓸 수 · 병합 수 · 올라간 원격 브랜치 · 다른 브랜치 · 태그). `RewordCommit` — LibGit2Sharp `Refs.RewriteHistory` 로 **고른 커밋의 메시지만** 갈고 트리 · 작성자 · 작성 시각 · 부모 관계는 그대로. 바꾸기 전 자리는 `refs/original/날짜-시각/` 에 남긴다(같은 자리에 두 번 쓰면 libgit2 가 막아서 시각을 붙임). 고친 커밋의 새 해시도 돌려준다 |
| `Views/RewordWindow.xaml(.cs)` (새 창) | 새 메시지를 받는 창. 고친 커밋 한 줄(해시 · 작성자 · 날짜) · 몇 개가 다시 써지는지를 늘 보이고, **여러 줄**을 받는다. 지금 메시지를 미리 넣어 전체를 골라 둔다. [바꾸기] 는 기본 단추가 아니다(Enter 는 줄바꿈) |
| `Views/GitView.xaml(.cs)` | 이력 목록의 우클릭 메뉴 **[메시지 바꾸기...]** — MergeHub 의 첫 우클릭 메뉴. 목록은 오른쪽 누름으로 선택이 안 바뀌므로 **우클릭한 줄을 먼저 고른다**(10차 B-1 처럼 저장 안 한 편집이 있으면 먼저 묻고 [취소] 면 메뉴도 안 연다) |
| `ViewModels/GitViewModel.cs` | 막힐 자리면 메뉴를 끄지 않고 **눌렀을 때 까닭을 알림으로** — 까닭은 저장소를 읽어 봐야 알고, 꺼 두면 왜 안 되는지 알 길이 없다. 되면 창 → 확인 창 → 실행 → 고른 커밋이 **새 해시**를 따라간다 |
| 하네스 `MergeSaveTest` | **+40건(232 → 272)** — 막는 여덟 가지와 보는 순서 · 확인 글 · 완료 알림 · 빈 메시지 · 안 바뀐 메시지 · 다듬기 아홉 |
### 화면에서 찾아 고친 것 — 끝 줄바꿈
첫 회차에 새 메시지가 **줄바꿈 없이** 저장됐다(`셋` 으로 끝남). git 이 만든 커밋은 늘 `\n` 으로 끝나고, MergeHub 의 [커밋] · [커밋 수정]도 libgit2 가 다듬는다. 칸을 어떻게 고쳤느냐에 따라 끝 줄바꿈이 있다 없다 했다.
libgit2 의 다듬기(`PrettifyMessages`)를 켜면 **손대지 않은 뒤 커밋들 메시지까지** 다듬어지므로, 새 메시지 하나만 `CommitReword.Clean` 으로 다듬는다(줄 끝 공백 · 앞뒤 빈 줄 없앰, 빈 줄 여럿은 하나, 끝은 줄바꿈 하나, `#` 줄 · 들여쓰기는 둠). 검사 9건을 먼저 넣어 실패를 보고 고쳤다.
같은 회차의 다른 둘은 **스크립트 탓**으로 가렸다(`e11-c2-probe.ps1`): 첫 우클릭이 안 먹은 것은 창을 앞으로 부르는 도우미가 다른 프로그램 창을 앞으로 가져와 클릭이 앱에 안 닿은 것, 한글이 한 글자만 들어간 것은 8ms 간격으로 몰아 친 것. 150ms 간격으로 치면 그대로 들어갔다.
### 고친 뒤 화면에서 (`e11-c2-setup.sh` · `e11-c2a.ps1` · `e11-c2b.ps1` · `e11-c2c.ps1`)
시험 저장소: `reword` 브랜치에 커밋 넷(작성자 박지훈 · 이수진, 셋째는 일부러 **"셋제 커밋"** 오타). 막힐 자리로 `pushed`(bare 원격에 올림) · `merged`(사이에 병합) · `shared` + `shared-copy` · `tagged`(v9.9).
**되는 자리** — "셋제 커밋" 우클릭 → [메시지 바꾸기...]
| 무엇 | 결과 |
|---|---|
| 우클릭 | 그 줄이 먼저 골라지고 메뉴 `메시지 바꾸기...` |
| 창 | `83ed0b2 · 박지훈 · 2026-09-16 11:00` · "이 커밋과 그 뒤 1개, 모두 2개가 새 해시로 다시 써집니다" · 칸에 `셋제 커밋` (`e11-c2-window.png`) |
| [취소] | HEAD `4c0263e` 그대로 |
| "셋째 커밋" 치고 [바꾸기] | 확인 창 "83ed0b2 "셋제 커밋" 의 메시지를 바꿉니다. 이 커밋과 그 뒤 1개, 모두 2개가 새 해시로 다시 써집니다. 내용 · 작성자 · 작성 시각은 그대로입니다. 바꾸기 전 HEAD 4c0263e 는 로그에 남겨…" (`e11-c2-confirm.png`) |
| [예] | 알림 `메시지 바꿈 완료 (옛 HEAD 4c0263e → 새 HEAD 1b11c03) · 커밋 2개 다시 씀` · 로그 `… · 되돌릴 자리 refs/original/20260922-102604/` |
| git 대조 | 넷째 · 셋째: **트리 해시 · 작성자 · 작성 시각 같음**, 해시만 새것. 제목은 셋째만 바뀜. 둘째 아래는 해시까지 그대로 · 커밋 수 같음 · 워킹트리 깨끗 |
| 메시지 끝 | `셋째 커밋\n` — 손 안 댄 넷째(`넷째 커밋\n`)와 같은 꼴 |
| 고른 커밋 | 새 해시 `b2d9880` 을 따라감 |
| 되돌리기 | `git reset --hard refs/original/20260922-102604/heads/reword` → `4c0263e … 셋제 커밋` 이 그대로 돌아옴 |
**막히는 자리** — 모두 창이 안 뜨고 HEAD 그대로, 알림에 까닭
| 자리 | 알림 |
|---|---|
| 올린 커밋 | "이 커밋은 origin2/pushed 에 이미 올라가 있습니다. 올린 커밋을 다시 쓰면 남의 이력과 갈라져 막습니다." |
| 사이에 병합 | "고른 커밋과 HEAD 사이에 병합 커밋이 1개 있습니다. 병합 이력은 다시 쓰지 않습니다." |
| 다른 브랜치도 품음 | "이 커밋을 다른 브랜치도 품고 있습니다(shared-copy). 그 브랜치까지 다시 써야 해서 막습니다." |
| 구간에 태그 | "다시 쓸 구간에 태그가 있습니다(v9.9). 태그가 가리키는 커밋이 사라져 막습니다." |
| 작업 중(병합 충돌 26개) | "병합 · rebase · cherry-pick · revert 를 끝낸 뒤에 쓸 수 있습니다." |
| 커밋 안 한 변경 | "커밋하지 않은 변경이 있습니다. 먼저 커밋하거나 stash 한 뒤에 바꾸세요." |
| 다른 브랜치에만([모든 브랜치]) | "지금 브랜치에서 닿지 않는 커밋입니다. 그 커밋이 있는 브랜치로 체크아웃한 뒤에 바꾸세요." (`e11-c2-other.png`) |
| 파일 비교 탭에 저장 안 한 편집 | 우클릭하자 `저장하지 않은 편집` 창("Common/Util/Unit103.cs 에 저장하지 않은 편집이 있습니다. 다른 커밋을 봅니다.") → [취소] 면 메뉴도 안 열리고 선택 그대로 (`e11-c2-unsaved.png`) |
- 하네스 22벌 **1,247건** 통과(1,207 → +40) · 빌드 경고 0 · 오류 0 · 설정 파일 해시 같음.
### 알아 둘 것
- 판단은 **마지막 fetch 시점** 기준이다. fetch 전에 남이 올린 것까지는 모른다(C-1 과 같음).
- 되돌릴 자리 `refs/original/…` 은 이력 목록에 안 보인다(목록은 브랜치만 훑음). 지우는 단추는 두지 않았다 — 되돌릴 일이 없으면 그대로 두어도 된다.
- HEAD 커밋도 이 메뉴로 바꿀 수 있다. [커밋 수정] 과 달리 **스테이징한 파일이 들어가지 않는** 메시지만 고치는 길이다.
## D-1. 시험 도구 둘 (2026-09-22, 완료 — 제품 코드 변경 없음)
| 도구 | 고치기 전 | 고친 것 |
|---|---|---|
| `d1-verify.ps1` (줄끝 · BOM · 표식 검사) | 부모 하나인 커밋(rebase · cherry-pick · revert)에도 없는 둘째 부모로 `git merge-base` 를 불러 사용법 글 · `null` 오류를 찍었다(판정 숫자는 맞았음) | 부모가 하나면 **첫 부모와만 견준다고 적고** merge-base 를 부르지 않는다. 견줄 부모 쪽 파일이 없는 새 파일은 가운데 BOM · 줄끝 섞임만 본다 |
| `d7lib.ps1` 의 `FileState` (파일 비교 탭 요약 읽기) | 요약 글을 "남은 충돌 · 충돌 정리 끝 · 남은 차이" 로만 찾아, 제품이 쓰는 **"차이 없음"**(편집이 있으면 "차이 없음 · 편집 N건 …")을 못 읽고 커밋 뒤 요약을 빈칸으로 적었다 | "차이 없음*" 도 요약으로 읽는다 (`HomeViewModel.cs:3486` 의 글과 맞춤) |
확인:
- 앱 없이 — 시험 저장소 HEAD(revert 커밋 `d59bc42`, 부모 하나)에 돌려 **오류 · 사용법 줄 0**, 결과는 E-1 revert 때와 같음(36개 · 다른 파일 0).
- 판정대로 rebase 한 길을 다시(`e11-e1-merge.ps1 -Way rebase`, 298초) — **오류 줄 0**, `[계속]` 뒤 파일 비교 탭 요약 **"차이 없음"**, "부모: … 하나 — 첫 부모와만 견준다", 누른 Take 18 = 로그 18 · 줄끝 · BOM 0/31 · 표식 0 · class 0 · 워킹트리 Clean · 설정 해시 같음.
- 고치기 전 두 파일은 `*.d1bak` 로 남김(시험 도구 폴더, 저장소 밖).
## E-1. 바꾼 길을 다시 끝까지 (2026-09-22, 완료 — 새로 걸린 것 없음)
11차에서 바뀐 것에 맞춰 넷을 돌렸다. A(변경분 추출) · C-1 · C-2 를 고쳤고, F-12 가 비교 · 저장에 걸리는 C# 스타일 기본값을, B-2 가 플랫폼 시작부를 바꿨으므로 **다섯 길 + pull** 도 다시 했다. 모두 Debug 출력(배포한 플랫폼 09-22 11:27 과 해시 같음), 사람 순서로(마우스 · 키보드).
### 1. A-1 의 아홉 경우 (`e11-a1-setup.sh` · `e11-e1-a.ps1` · `e11-e1-a-verify.sh`)
모두 첫 회차에 끝. 뽑은 곳은 고른 폴더 **아래 새 폴더**, 안내는 그 옆(A-2 · A-3 설계대로).
| # | A-1 때 | 지금 |
|---|---|---|
| 1 커밋 하나 | 2개 · 바이트 같음 | `변경분_228995f..8145f3b_2026-09-22` 에 2개 · 바이트 같음 · 안내 범위 "(고른 커밋의 부모)" |
| 2 범위 | 3개 · 바이트 같음 | 3개 · 바이트 같음 · 안내에 이름 바뀜 "(옛 이름 Motion/Unit16.cs)" |
| 3 지운 파일 | **어디에도 없음** | 안내 "장비에서 지워야 할 파일" 에 `지워짐 Recipe/Unit11.cs` |
| 4 이름 바뀐 옛 경로 | **알 길이 없음** | 같은 목록에 `옛 이름 Motion/Unit16.cs (→ Motion/Renamed16.cs 로 바뀜)` — git 기대 지울 것 2개와 같음 |
| 5 병합 커밋 | 첫 부모와만 · 화면에 없음 | 같은 동작(1개) · **안내에 범위 `37a2ced … (고른 커밋의 부모) → 84961da`** 로 무엇과 견줬는지 남음 |
| 6 거꾸로 범위 | 말없이 됨 | 같은 동작(2개 · 지울 것 3개) · 안내에 방향 그대로(`37a2ced → 228995f`) |
| 7 이진 · 한글 · 깊은 경로 | 바이트 그대로 | 바이트 그대로 |
| 8 이미 파일이 있는 폴더 | 옛 추출 · 사용자 파일과 **섞임** | 두 번 뽑으니 **새 폴더 둘**(범위별 이름) · `사용자메모.txt` 그대로 |
| 9-a · 9-b 읽기 전용 · 잠긴 파일 | **반쯤 뽑힌 채 멈춤** | 새 폴더에 써서 막힘 없이 3개 · 미리 둔 파일은 손대지 않음 |
- 대조 스크립트가 경우 8 의 첫 폴더(c1 범위)를 둘째 범위와 견줘 "다름" 이라고 찍었다 — 그 폴더는 안내대로 c1 범위이고 경우 1 과 같은 내용이다(스크립트가 폴더마다 범위를 따로 안 봄).
- 저장소 워킹트리 내내 Clean.
### 2. C-1 의 여덟 경우 (`e11-c1-setup.sh` · `e11-e1-c.ps1` · `e11-e1-c1again.ps1`)
| # | C-1 고치기 전 | 지금 |
|---|---|---|
| 1 | [모든 브랜치] 에서 [마지막 메시지] 가 딴 브랜치 메시지 | 목록 맨 위 `딴 브랜치 최신 작업` 인데 칸에 **`남이 만든 커밋`**(HEAD) |
| 2 | 스테이징 파일이 말없이 들어감 | 확인 창 "스테이징해 둔 파일 1개도 이 커밋에 들어갑니다" → [예] → 알림 "… · 파일 1개 포함" |
| 3 | 작성자 · 작성 시각이 바뀜 | 두 번 고쳐도 작성자 `박지훈` · 작성 `2026-09-15 10:00` 그대로 |
| 4 | 작업 중에도 켜져 실패 | 병합 충돌 26개 동안 **꺼짐** · 설명 "병합 · rebase · cherry-pick · revert 를 끝낸 뒤에 쓸 수 있습니다" |
| 5 | (부모 둘 그대로) | [마지막 메시지] 가 `Merge branch e1-side` · 고쳐도 **부모 둘** |
| 6 | 막지 않고 Push 거부 | 확인 창 "이 커밋은 origin/amend 에 이미 올라가 있습니다. 고치면 다음 Push 가 거부됩니다" → [예] 뒤 [Push] `cannot push non-fastforwardable reference` (정한 대로 묻기만) |
| 7 | 새 해시만 | 알림 "커밋 수정 완료 (옛 4816f3e → 새 953816a)" |
| 8 | 한 줄 칸 | 그대로(범위 밖으로 둠) |
### 3. C-2 로 이력을 다시 쓴 저장소에서 병합 한 길 (`e11-e1-merge.ps1 -Way reword`)
새 저장소의 `feature/mine` 에 커밋 둘(박지훈 · 이수진)을 얹고, 앞 커밋 `첫 추가 커밋(오타 잇음)` 을 우클릭 → [메시지 바꾸기...] 로 `첫 추가 커밋` 으로 고친 뒤 그대로 병합.
- 확인 창 "… 이 커밋과 그 뒤 1개, 모두 2개가 새 해시로 다시 써집니다 …" → 알림 `메시지 바꿈 완료 (옛 HEAD e1d924c → 새 HEAD 3145f3f) · 커밋 2개 다시 씀`.
- 앞 네 커밋의 작성자 · 작성 시각 · **트리 4/4 같음**, 제목은 고른 것만. 되돌릴 자리 `refs/original/20260922-115615/heads/feature/mine`.
- 병합: 충돌 24(+자동 2) → 누른 Take 18 = 줄 고른 로그 18 → 병합 커밋 `ae20f56`(부모 첫째 = 다시 쓴 `3145f3f`) · 줄끝 · BOM 바뀐 파일 0 · 충돌 표식 0 · class 0 · 워킹트리 Clean · 두 탭 같은 커밋.
### 4. 다섯 길 + pull (`e11-e1-merge.ps1 -Way merge|pull|rebase|pick|revert` — 10차 E-1 과 같은 순서)
| 길 | 누른 Take = 로그 | 마지막 Save 뒤 기준 | 커밋 | 줄끝 · BOM 바뀐 파일 | 충돌 표식 · class | 두 탭(10차 B-1) | 걸린 시간 |
|---|---|---|---|---|---|---|---|
| 병합 | 18 = 18 | HEAD · N=3 = git | `cac5366` 부모 둘 · 본문 해결 26개 | 0/33 | 0 · 0 | 같음 | 303초 |
| pull | 18 = 18 | 같음 | `1b32e2f` "Merge remote-tracking branch 'origin/feature/mine' …" | 0/33 | 0 · 0 | 같음 | 303초 |
| rebase | 18 = 18 | 같음 | `b108182` 부모 하나 · "내 쪽 작업" | 0/31 | 0 · 0 | 같음 | 302초 |
| cherry-pick | 18 = 18 | 같음 | `631234e` "상대 쪽 작업" · 빈 cherry-pick 은 [커밋] 꺼짐 → [작업 취소] (8차 B-2) | 0/33 | 0 · 0 | 같음 | 350초 |
| revert | 6 = 6 | HEAD · N=6 = git | `d59bc42` `Revert "내 쪽 작업"` · REVERT_HEAD 안 남음 | 0/36 | 0 · 0 | 같음 | 199초 |
- 10차 E-1 과 숫자가 같다. **새로 걸린 것 없음.**
### 시험 도구 쪽에서 고친 것 (제품 아님)
- **창을 앞으로 부르는 도우미가 최대화를 풀었다** — `ShowWindow(SW_RESTORE)` 를 늘 불러, 첫 누름 때 창이 복원 크기로 줄고 옛 자리를 눌러 로그 칸에 떨어졌다(누르기 전 · 뒤 사진으로 확인). 경우 1 이 처음에 "칸이 빔" 으로 나온 까닭이다. **최소화돼 있을 때만** 되살리게 고쳤다(`e11-c1lib.ps1` · `e11-e1-a.ps1`). 앞선 차수의 결과는 누른 뒤 자리를 다시 읽어 눌렀으므로 그대로 믿을 수 있다.
- C-1 시험 저장소의 "다른 브랜치의 더 새 커밋" 날짜가 09-18 이라 오늘(09-22)에는 목록 맨 위가 HEAD 가 되어 경우 1 이 성립하지 않았다 → 12-31 로.
- rebase · cherry-pick · revert 뒤 `d1-verify.ps1` 이 부모가 하나인 커밋에도 `git merge-base` 를 불러 사용법 글을 찍는다 — 판정에는 영향 없음(10차와 같음).
- 이 PC 의 Caps Lock 이 켜져 있어 영문 입력이 대소문자 뒤집혀 들어갔다(경우 5 의 메시지). 키보드 상태라 바꾸지 않았다 — 판정에는 영향 없음.
- 하네스 22벌 1,247건 · 플랫폼 24건 그대로 · 빌드 경고 0 · 오류 0 · 설정 파일 되돌림 해시 매번 같음. 제품 코드 변경 없음.
## E-2. 웹 문서 · 기획안을 11차에 맞게 (2026-09-22, 완료)
### MergeHub 문서 (`doc/web/index.html`, 아티팩트 "MergeHub 문서" 버전 11 → **13**)
- 올리기 전에 발행본(버전 11)을 받아 로컬과 견줌 — 09-17 에 올린 그대로였고(다른 사람 손 없음), 그 뒤 로컬에서만 바뀐 것은 11차 F-12 · B-1 · B-2 문장뿐.
- 고친 것:
- 개요 — 09-22 기준 · 22벌 1,247건 · 11차 · 커밋 129 · 지시서 11차.
- 매뉴얼 — Git 탭 표에 우클릭 [메시지 바꾸기...] 와 아래 두 절 연결, **"커밋 메시지 고치기"**(두 길을 한 표로 — 고치는 커밋 · 메시지 넣기 · 함께 들어가는 것 · 이미 올린 커밋 · 남는 것 · 꺼지는 때, 되돌리는 길), **"변경분 추출"**(새 폴더 · 옆 안내 파일 · 먼저 보고 쓰기 · 병합 커밋 · 거꾸로 범위), 목차 둘, 알아 둘 한계 셋(메시지 칸 한 줄 · 올렸는지는 마지막 Fetch 기준 · D 없는 PC 는 계정마다 설정). 실행 위치(B-1) · 설정 위치(B-2) · C# 스타일 기본 AURORA(F-12)는 먼저 적어 둔 것이 함께 올라감.
- 남은 일 — 11차 표(완료 · 대기 · 진행).
- 지금까지 만든 것 — 09-22 11차 기록 · 커밋 129 · 1,247건 · 하네스 막대 11차(폭을 새 최댓값으로 다시 잼) · 기능 목록 Git 한 줄 · 배포 한 줄.
- 기획안 v2 — 커밋 메시지 수정 **완료**, 변경분 추출 설명, 3단계에 11차, 4단계 **진행**(베타 준비).
- WinMerge 쪽 숫자 1,247건.
- 대조: 문서의 단추 이름 91개를 소스(MergeHub · 플랫폼)에서 찾음 — **"맞추지 않음"(09-18 F-12 때 적은 말)이 실제 콤보 글자 "C# 스타일 끔" 과 달라** 문서 두 곳 · 이 기록 한 곳을 고침. 남은 하나(`비교 규칙 펴기` 의 공백 두 칸)는 화면에 보이는 글자가 같다. 태그 짝 모두 맞음.
- 공유 링크를 받은 사람에게는 **고정된 옛 버전**이 보이도록 되어 있다 — 새 버전을 보이려면 페이지 Share 메뉴에서 사용자가 바꾼다.
### 기획안 v1.2 (잠금 아티팩트 "UbiSam MergeTool 개발 기획안" 버전 **17**)
- 발행본을 받아 로컬 잠금 파일(09-17 17:06)과 견줌 — 암호문 · salt · IV 가 같음. 그러므로 고칠 원문은 09-17 원문(`plan/mergetool.html`).
- 원문에서 고친 것: 3.2 Git 작업 UI화의 **커밋 메시지 수정 → 완료**("마지막 커밋은 [커밋 수정], 앞 커밋은 트리에서 우클릭 → [메시지 바꾸기]"), 변경분 파일 추출 설명(새 폴더 · 지울 파일 안내, ZIP 이 없어 "일부" 그대로), 일정 요약(09-22 기준 · 1,247건 · 4단계 베타 준비 중), 3단계 "Git 작업 버튼" 한 줄, 4단계 상태 "대기" → "준비 중", 4단계 베타 줄, 개정일 09-22.
- **A4 쪽 높이**: 고치기 전 · 뒤 원문을 헤드리스 Edge 로 재니 8쪽 모두 1123px · 넘침 없음으로 같다. 기능 목록 쪽 그림으로 한 줄씩인 것 확인.
- **잠금은 사용자가 했다**(`lock-plan.js`, 비밀번호는 화면에 안 보이게 받아 어디에도 남기지 않음 · 비교할 원본은 09-17 원문). 첫 "잠갔어" 때는 잠금 파일이 09-17 것 그대로여서 올리지 않고 멈췄다(PowerShell 문법을 CMD 에서 돌린 것으로 보임). 두 번째에 새 salt · IV 의 잠금 파일(13:12)이 생겨 올렸고, 발행본의 암호문이 그 파일과 같음을 되읽어 확인했다. 스크립트 출력은 다른 창이라 직접 보지 못했다.
## F-1. 칸 이름을 맞추고 칸마다 어느 파일인지 · 머지 주석에 어디서 가져왔는지 (2026-09-22, 완료)
사용자 결정(2026-09-22): 칸 이름은 **표준 용어 + 영문 쪽 이름**(`LOCAL(Mine)` · `REMOTE(Theirs)` — 4차 · 6차의 "표준 용어 그대로" 를 이어 가고 로그의 Mine/Theirs 와 맞춤), 주석 출처는 **쪽 이름 + 브랜치/해시**, 설명이 있으면 **출처 먼저, 설명 뒤**.
### 고치기 전 — 화면에서 (`f1-probe.ps1`)
| 길 | 칸 머리에 보인 것 |
|---|---|
| 병합 네 칸 | BASE `(merge base) Weld/Unit1.cs` · LOCAL (Mine) `feature/mine` · MERGED `feature/mine` · REMOTE (Theirs) `REMOTE · feature/theirs` — **파일 이름은 BASE 칸에만**, REMOTE 칸은 제목의 말을 되풀이 |
| 두 칸 | BASE `9b064cc · Weld/Unit1.cs` · LOCAL `feature/mine` — 저장하는 칸에 파일이 없음 |
| git mergetool · `--merge` (코드로) | **Mine 칸이 MERGED 칸과 같은 값**(저장할 파일 이름)을 써서, 실제로 읽은 `--local` 임시 파일과 다른 이름이 보였다 |
머지 주석은 `// [김승준] 2026-09-22` 뿐이고 Take Mine 은 주석을 남기지 않았다(로그에만 "정함").
### 고친 것
- **칸 머리** — 칸마다 한 줄 "어느 쪽 · 어느 파일"(`PaneHeaders`). 네 칸: `feature/mine · Weld/Unit1.cs` · `(merge base) Weld/Unit1.cs` · `Weld/Unit1.cs (저장할 파일)` · `feature/theirs · Weld/Unit1.cs`. 두 칸: `3a12077 · Weld/Unit1.cs` · `feature/mine · Weld/Unit1.cs (저장할 파일)`. mergetool · `--merge`: 네 칸이 제 임시 파일 이름(Mine 칸은 새 `MineHeader`). rebase 는 `올릴 자리 …`, cherry-pick · revert 는 `가져오는 커밋 …` · `되돌리는 커밋 …` 에 파일이 붙는다.
- 제목 `LOCAL (Mine)` · `REMOTE (Theirs)` → `LOCAL(Mine)` · `REMOTE(Theirs)`. 네 칸 머리 글씨를 모두 BASE 와 같은 모양(읽으라고 쓰는 글씨)으로.
- **한 줄 · 가운데 줄이기** — WPF 끝 자르기는 파일 이름부터 잘라서, 폭에 맞게 가운데를 `…` 로 줄이는 `MiddleTrim`(앞 조각 굵게는 그대로). 전체는 툴팁.
- **머지 주석 출처** — `[사람] 날짜 출처 설명`(`MergeNote.Build` 에 출처). 출처는 로그와 같은 말(`MergeNames.TheirsSource` · `MineSource` · `BaseSource`): 네 칸 `Theirs(feature/theirs)` · `Mine(feature/mine)`, rebase · cherry-pick · revert `Theirs(커밋 42e68b2)`, 브랜치 3-Way `REMOTE(develop)`, 두 칸 `BASE(3a12077)`(Git 아니면 왼쪽 파일 이름).
- 로그도 같은 말로 — 두 칸 `9행 BASE 반영` → `9행 BASE(af3925e) 반영`, 브랜치 3-Way `7행 develop 반영` → `7행 REMOTE(develop) 반영`. 네 칸 로그는 원래 같은 말이라 그대로.
- **Take Mine 도 주석을 남긴다**(지시서 판정 기준) — 병합의 두 쪽에서 내 것으로 정하면 `Mine(…)` 주석 한 줄. 같은 줄을 또 눌러도 쌓이지 않고(처음 한 번), 되돌리기가 주석 줄까지 뺀다. 브랜치 3-Way 의 "내 원본으로 되돌림" 은 되돌리기라 주석 없음(그대로).
- **무시 조건** — 알아보는 규칙(`[사람] 날짜` 뒤는 따지지 않음)이 원래 출처를 받아들이는 모양이라 `NoteBody` · `LooksLikeNote` · `TailStart` · `CompareRules` · `FolderComparer` · `NoteConflict` · `NoteRows` 는 코드를 바꾸지 않았다. 대신 옛 모양 · 새 모양 · 출처만 다른 모양을 하네스로 굳힘.
### 확인한 것
- 하네스: NoteTest(출처 자리 · 옛 모양 바이트 같음 · XAML 닫는 기호 · 줄 끝 조각 · 무시 키 · 출처만 다른 충돌 자동 해결 두 주석 다 남김 · 코드가 다르면 안 풂) · RulesTest(무시 · 규칙 끄면 차이) · FolderTest(출처만 다름 · 옛↔새 → **같음(머지 주석만 다름)**, 규칙 끄면 다름(코드는 같음)) · MergeSaveTest(출처 = 로그의 말 · 칸 머리 · 가운데 줄이기). 22벌 **1,312건** 통과(1,260 → +52) · 빌드 경고 0 · 오류 0.
- 화면(`f1-verify.ps1` · `f1-trim.ps1`, UIA 만 — 마우스 안 씀):
| 길 | 칸 머리 | 누른 것 → 로그 | 파일에 남은 주석 |
|---|---|---|---|
| 병합 네 칸 | `feature/mine · Weld/Unit1.cs` · `(merge base) Weld/Unit1.cs` · `Weld/Unit1.cs (저장할 파일)` · `feature/theirs · Weld/Unit1.cs` | `101행 Theirs(feature/theirs) 반영` · `802행 Mine(feature/mine) 으로 정함` (Take Mine 한 번 더 → 단추 꺼짐) | `// [김승준] 2026-09-22 Theirs(feature/theirs)` · `… Mine(feature/mine)` — 범례 무시 2 |
| 두 칸 | `af3925e · Weld/Unit1.cs` · `feature/mine · Weld/Unit1.cs (저장할 파일)` | `9행 BASE(af3925e) 반영` | 화면의 주석 줄 `// [김승준] 2026-09-22 BASE(af3925e)`(두 칸은 Save & Commit 뿐이라 커밋하지 않고 화면에서 읽음) |
| `--merge` | `Unit1_LOCAL_4321.cs` · `Unit1_BASE_4321.cs` · `Unit1.cs (저장할 파일)` · `Unit1_REMOTE_4321.cs` | `Theirs(Unit1_REMOTE_4321.cs) 반영` · `Mine(Unit1_LOCAL_4321.cs) 으로 정함` | 같은 말 두 줄 |
| git mergetool (`git -c` 로만 도구 설정 — 설정 파일에 안 남음 확인) | `Unit1_LOCAL_290.cs` · `Unit1_BASE_290.cs` · `Unit1.cs (저장할 파일)` · `Unit1_REMOTE_290.cs` | `Theirs(Unit1_REMOTE_290.cs) 반영` | 같은 말 |
- 칸 머리는 모두 한 줄(높이 21). 117자 파일 이름으로 `--merge`: `WeldSequenceController_Laser…ipeParameterValidation_Unit1_LOCAL_4321.cs` 처럼 가운데가 줄고 끝(쪽 · 파일 이름)이 남음, 툴팁에 전체. 창 폭을 UIA 로 줄이는 것은 이 창에서 먹지 않아 긴 이름으로 대신 봄.
- 회귀: 11차 E-1 병합 길(`e11-e1-merge.ps1 -Way merge`, 마우스 확인 B-1 · 스크린숏만 끔) — 누른 Take 18 = 로그 18, 병합 커밋 본문 26개, 줄끝 · BOM 바뀐 파일 0 · 충돌 표식 0 · class 검사 0, 옛 모양 주석만 다른 2건 자동 해결 그대로. 결과 파일의 Take Mine 줄 위에 `Mine(feature/mine)` 주석이 새로 들어간다(이번 변경).
- 첫 번째 회차는 1) 파일 여러 개 골라 Accept Theirs 가 한 개만 골라져(24 → 23, 20초) 줄 단위로 처음 가 보는 파일들에서 Take 가 꺼져 있었다. 그 파일들을 새로 열어 보면 Take 둘 다 켜짐(`f1-take-probe.ps1`), 두 번째 회차는 1) 이 24 → 9(1초)로 예전과 같아 끝까지 — 시험 도구의 여러 개 고르기가 한 번 빗나간 것으로 본다.
### 알아 둘 것
- Take Mine 주석이 새로 들어가므로 같은 순서로 풀어도 결과 파일이 전보다 주석 줄 수만큼 길다(무시 규칙이 켜져 있으면 차이로 세지 않음). 코드 줄 · 줄끝 · BOM 은 그대로.
- 파일 단위 Accept(충돌 탭)는 여전히 소스에 주석을 남기지 않는다 — 병합 커밋 본문(8차 F-1)이 그 기록이다.
## F-2. 메서드 단위로 골라 반영 · 덩어리 반영의 주석 한 줄 (2026-09-22, 완료)
사용자 결정(2026-09-22): 언어는 **C# + C/C++**, "메서드" 는 **클래스 멤버 전부**(메서드 · 생성자 · 소멸자 · 속성 · 인덱서 · 이벤트 · 연산자 · 필드 · enum — 로컬 함수 · 람다 안이면 감싸는 멤버 통째), 덩어리 반영의 주석도 **첫 줄 위 한 줄로**.
### 고치기 전 — 화면에서 (`f2-probe.ps1`)
- 지시서에는 "덩어리를 반영하면 머지 주석은 덩어리 첫 줄 위에 한 줄" 이라고 적었지만 사실과 달랐다 — 이어진 두 줄 차이에 [이 덩어리 반영] → **주석이 줄마다 하나씩 두 줄**. `AcceptBlock` 이 줄마다 `Apply` 를 불러 줄마다 주석 · 로그 · 되돌리기 한 건씩이었다.
- 메서드 단위 반영은 없었다.
### 만든 것
- **`MemberRanges`**(새 서비스, Roslyn 없음) — 파일 전체를 훑어 주석 · 문자열을 가리고(C# 축자 · 보간 · raw `"""`, C++ raw `R"x(...)x"` · 숫자 구분자 `1'000` · 문자 `'{'`), 중괄호 짝으로 멤버 범위(시작 · 이름 줄 · 끝)를 잡는다.
- `#if` 는 갈래(#elif · #else)마다 `#if` 자리의 상태에서 다시 읽고 `#endif` 뒤에는 첫 갈래 상태로 잇는다 — 여는 줄만 갈린 메서드도 짝이 맞고, 갈래마다 따로 있는 메서드는 따로 잡힌다.
- 멤버 몸통이 아닌 중괄호: 필드 초기화(`= new X { }`) · 람다 · switch 식(`=>` 뒤) · 속성 초기화(`{ get; set; } = 1;`) · C++ 생성자 초기화 목록(`items_{1, 2}`) · 특성 안.
- 시작은 바로 위 설명 주석(`///` · `//`) · 특성 · 감싸는 `#if` 까지. **머지 주석에서 멈춘다**(이력이라 반영으로 지우면 안 됨). 클래스 · 네임스페이스 선언 줄은 어느 멤버에도 들지 않는다. 짝이 틀어져 겹치면 뒤의 것을 버린다(틀린 범위 대신 없음).
- **[이 메서드 반영]**(덩어리 반영 아래) — 캐럿이 놓인 멤버를 반영해 올 쪽(두 칸 BASE · 네 칸 REMOTE)에서 찾아, 그 범위의 행(사이의 내 쪽에만 있는 줄 포함)을 덩어리 반영과 같은 규칙으로 반영. 네 칸은 Take Theirs 와 같아 저쪽이 바꾼 줄 · 충돌 줄만, 내 쪽만 고친 줄은 그대로.
- 단추 아래 한 줄: 잡으면 `Target · BASE 7~17행 · 바뀐 줄 4`, 못 잡으면 까닭(`캐럿이 BASE 쪽 메서드 안에 있지 않습니다` · `C# · C/C++ 파일에서만` · `… 안에 가져올 차이가 없습니다`) — 단추는 꺼지고 툴팁도 같은 말.
- **묶음 반영**(`ApplyGroup`) — 덩어리 · 메서드 모두: 머지 주석은 **한 줄**(메서드는 이름 줄 위, 덩어리는 첫 줄 위, 출처는 F-1 과 같은 말), 로그도 한 줄(`7~11행 메서드 Target BASE(left.cs) 반영 (4줄)` · `10~11행 덩어리 BASE(left.cs) 반영 (3줄)`), **Undo · Redo 한 번에 통째로**(편집에 묶음 번호). 주석만 든 편집(`NoteOnly`)은 "직접 수정" · "편집 N건" 에 세지 않는다. 머지 주석 줄은 건드리지 않는다.
### 확인한 것
- 하네스 **MemberTest**(새, 83건): C# 시험 파일 29개 멤버 · C++ 13개 멤버의 시작 · 이름 줄 · 끝이 표시 주석과 모두 같다(한 줄 메서드 · 중첩 중괄호 · 문자열 · 축자 · 보간 · raw 안 중괄호 · 주석 안 중괄호 · `#if` 로 여는 줄이 갈린 메서드 · 갈래마다 다른 메서드 · 로컬 함수 · 람다 필드 · switch 식 · 속성 초기화 · 이벤트 · 인덱서 · 연산자 · 머지 주석 위 멤버 · 설명 주석 · 중첩 클래스 · C++ 템플릿 · 초기화 목록 · `operator=` · raw 문자열 · 숫자 구분자 · `extern "C"` · `enum class` · `#ifdef`), 기대하지 않은 범위 없음 · 겹침 없음, 이름, 캐럿 행 → 멤버 · 멤버 → 화면 행, 짝이 틀어진 파일에서 멈추지 않음. 23벌 **1,395건**(1,312 → +83) · 플랫폼 24건 · 빌드 경고 0 · 오류 0.
- 화면(`f2-verify.ps1`, `--diff` · `--merge` 로 C# 짝, BOM · CRLF):
| 길 | 결과 |
|---|---|
| 두 칸 · 주석 켬 | 연 직후 단추 꺼짐 + `캐럿이 BASE 쪽 메서드 안에 있지 않습니다…`, [다음 차이] 뒤 `Target · BASE 7~17행 · 바뀐 줄 4` · 켜짐. 반영 → 머지 주석 **1줄**(설명 주석과 선언 줄 사이, 메서드 이름 위) · 범례 반영 4 · 직접 수정 0 · 편집 4건. **Undo 한 번** → 주석 0 · 남은 차이 5줄(처음과 같음), Redo 한 번 → 다시. 저장한 파일: Target 은 BASE 그대로, 다른 메서드 Other 는 내 쪽 그대로 |
| 두 칸 · 주석 끔 | 메서드 반영 저장본 = **같은 네 줄을 하나씩 골라 Accept 한 저장본 = 기대(Target = BASE, 나머지 = 내 쪽, BOM · CRLF 그대로)** — SHA-256 같음 |
| 두 칸 · 덩어리 | [이 덩어리 반영] 세 줄 → 머지 주석 **1줄**, Undo 한 번에 처음으로 |
| 세 칸 `--merge` | `Target · REMOTE 7~18행 · 바뀐 줄 2` → 주석 `Theirs(Station_REMOTE_1.cs)` 한 줄, 저장본 Target = REMOTE(`limit = 150` · `Trace(power);`), 내가 고친 Other(`return 2;`)는 그대로 |
- 회귀: 11차 E-1 병합 길(마우스 확인 B-1 · 스크린숏만 끔) — 누른 Take 18 = 로그 18, 병합 커밋 본문 26개, 줄끝 · BOM 바뀐 파일 0 · 충돌 표식 0 · class 검사 0.
- E-1 의 1) "15개 골라 Accept Theirs" 가 가끔 24 → 23 에서 멈췄다(F-1 · F-2 회차에 모두 12번 중 3번). 따로 재 보니(`f2-multisel.ps1`) 첫 Select 뒤 AddToSelection 이 하나도 안 먹은 것 — 충돌 목록에서 고를 때 앱은 단추 상태만 새로 고치고 F-1 · F-2 가 고친 파일 비교 쪽은 타지 않는다. F-1 전 빌드(`2f36f1b`, 임시 폴더에 꺼내 빌드) 5번 · 지금 빌드 5번 모두 15개 골라짐. 병합 직후 목록이 한 번 더 그려지며 시험 도구가 잡아 둔 항목이 낡은 것으로 보고, **시험 도구가 고른 수를 세어 모자라면 목록을 다시 잡아 다시 고르게** 고쳤다(`d7lib.ps1`, 제품 아님).
### 남은 것 · 알아 둘 것
- [전체 반영](두 칸) · [자동 병합](네 칸)은 지금도 줄마다 주석이 붙는다 — 이번에 정한 것은 덩어리 · 메서드. 같은 규칙(한 줄)으로 맞출지는 정하지 않았다.
- 범위는 반영해 올 쪽(BASE · REMOTE) 글로 잡는다. 그쪽에 없는 메서드(내 쪽에서만 새로 만든 메서드)는 잡히지 않아 단추가 꺼진다.
- XAML · 그 밖의 언어는 단추가 꺼지고 까닭을 말한다(사용자 결정: C# + C/C++).
## F-3. 탐색기에서 폴더 두 개를 [보내기] 로 바로 비교 · 설치 없이 되는지 (2026-09-22, 완료)
### 어떻게 쟀나 — 사용자의 보내기 메뉴는 건드리지 않고
탐색기 [보내기] 는 SendTo 폴더의 바로가기에 고른 항목들을 **떨어뜨리는 것**(바로가기의 IDropTarget::Drop)이다. 같은 셸 길을 부르는 도구(`SendToSim` — 고른 항목의 데이터 개체를 임시 폴더의 바로가기에 Drop)와 받은 명령줄을 적기만 하는 `ArgsEcho` 로 쟀다. 바로가기는 [탐색기 보내기 등록] 과 같은 모양(대상 · 작업 폴더).
| 경우 | 탐색기가 넘긴 것 | 고치기 전 MergeHub |
|---|---|---|
| 파일 2개 | **프로세스 1개에 인자 2개** | 파일 비교로 열림 |
| 폴더 2개 | 프로세스 1개에 인자 2개 | **창 하나에 왼쪽 · 오른쪽 폴더, 폴더 비교까지**(`전체 1개 · 같음 1`) |
| 폴더 1개 | 인자 1개 | 폴더 비교, 왼쪽만 채움(오른쪽은 사용자가 고름) |
| 3개 이상 | 프로세스 1개에 인자 3개 | 앞의 둘만 비교, 셋째는 **말없이 버림** |
| 경로에 공백 | 그대로(`left side`) | 폴더 비교까지 |
| 한글 | 그대로(`왼쪽 폴더`) | 폴더 비교까지 |
| 260자 넘는 경로 | **8.3 짧은 이름**(123자, `ME0359~1\긴폴더~1\…\RIGHTL~1`) | **아무것도 안 열림** — 로그엔 폴더인데 "파일 비교 요청" |
- 탐색기는 여러 개를 **한 번, 프로세스 하나**로 넘긴다 — "두 번 뜬 프로세스를 하나로 모으는 길" 은 필요 없다.
- 긴 경로: net48 에서 짧은 이름을 `Path.GetFullPath` 가 긴 이름(277자)으로 펼친 뒤 못 찾는다(`Directory.Exists` 거짓, `\\?\` 를 붙이면 "잘못된 문자" — `LongPathProbe`). 열려면 플랫폼 실행 파일 설정(긴 경로 스위치)을 바꿔야 한다.
- MergeHub 가 **이미 떠 있을 때** 보내면 창이 하나 더 뜬다(프로세스 2개).
### 정한 것 (2026-09-22 사용자)
- 긴 경로: **열 수 없다고 알린다**(지원은 플랫폼 설정을 바꿔야 해서 하지 않음).
- 3개 이상: **앞의 둘 + 알림**.
- 배포 묶음을 옮겨 바로가기가 옛 자리를 가리키면: **켜질 때 저절로 고친다**.
- 이미 떠 있을 때: **지금처럼 새 창**(하나로 모으려면 플랫폼에 한 번만 실행 · 창 사이 전달이 필요 — 남은 것).
### 고친 것
- `StartupArgs` — 셋째부터는 `Extra`, 앞의 둘 가운데 파일도 폴더도 아닌 것은 `Missing`(있으면 **아무것도 열지 않는다**). 알림 한 줄 `Notice`: `3개를 골랐습니다 - 앞의 둘만 비교하고 1개(Third)는 두었습니다` · `LeftLong 외 1개 - 경로가 277자라 열 수 없습니다(260자가 넘는 경로는 아직 열지 못합니다)` · 그 밖은 `… - 찾을 수 없습니다`. 이름표 붙은 경로(`git difftool` 의 지워진 쪽)는 따지지 않는다.
- `GitWorkspace` — 켤 때 넘어온 것 · 끌어다 놓은 것 모두 같은 말로 로그 한 줄 + 경고 알림. 켤 때는 사용자 이름 창(모달)이 먼저 떠서 그 뒤로 알림이 지나가 버렸다 — **본 창이 활성이 된 뒤에** 띄운다(이름 창을 닫고 0.4초 뒤에 보임).
- `ShellIntegration.RepairIfMoved` — 등록해 둔 사람만, 바로가기 대상이 지금 실행 파일과 다르면 **바로가기 파일만** 다시 쓰고 로그 한 줄(`보내기 바로가기가 옛 자리(…)를 가리켜 지금 실행 파일로 고쳤습니다`). 레지스트리는 손대지 않는다. 이미 맞으면 파일을 건드리지 않는다.
### 설치 파일 없이 되는가
**된다.** 보내기는 `%APPDATA%\Microsoft\Windows\SendTo` 에 바로가기 하나를 두는 것이 전부다(관리자 권한 · HKLM · 설치 관리자 없음). 배포 묶음을 옮기면 켤 때 바로가기가 따라온다. 레지스트리에 닿는 것은 [탐색기 보내기 등록] 을 누를 때 **예전 판이 HKCU 에 남긴 항목이 있으면 지우는 것** 하나뿐이다(새로 쓰지 않음).
### 확인한 것
- 하네스 ArgsTest +16(52건): 3개 → 앞의 둘 · 남은 것 · 알림 글, 2개 · 1개 → 알림 없음, 없는 경로 → 안 열고 그 경로 · "찾을 수 없습니다", 260자 넘는 경로 → 안 열고 "260자", 이름표 붙은 difftool 경로는 안 따짐, 빈 인자, 바로가기 대상 판정(같은 곳 · 대소문자 · 빈 대상 · 지금 실행 파일 모름). 23벌 **1,411건** · 빌드 경고 0 · 오류 0.
- 화면(`f3-sendto.ps1` · `f3-e2e.ps1` · `f3-e2e2.ps1` · `f3-toast.ps1` · `f3-second.ps1`, 바로가기 대상을 실제 MergeHub Debug 로):
- 폴더 2개 · 공백 · 한글 → 창 하나에서 폴더 비교까지(로그 `폴더 비교: 왼쪽 ↔ 오른쪽 · 전체 1개 · 같음 1`). 파일 2개 → 파일 비교.
- 3개 → 폴더 비교(앞의 둘) + 로그 · 알림 `3개를 골랐습니다 - …1개(Third)는 두었습니다`, 알림은 이름 창을 닫은 직후.
- 긴 경로 → 아무것도 열지 않고 로그 · 알림 `LeftLong 외 1개 - 경로가 277자라 열 수 없습니다…`.
- 바로가기 저절로 고침: 사용자의 실제 바로가기를 바이트 그대로 떠 두고 대상을 `D:\옛자리\bin\…` 로 바꿈 → 켜자 지금 실행 파일 · 작업 폴더로 고쳐지고 로그 한 줄, 한 번 더 켜도 파일 그대로 → 원래 파일 **바이트 그대로 되돌림**(원래 대상이 이미 Debug 실행 파일이라 평소 시험으로는 바뀌지 않는다).
- 시험 재료(`%TEMP%\mergehub-f3`, 긴 경로 포함)는 지웠다.
### 사용자가 실제 탐색기에서 찾은 것 — 왼쪽 칸이 기본 저장소로 덮임 (2026-09-22, 고침)
사용자: "폴더 2개를 선택해 보내기에서 머지허브를 켰는데 한개의 폴더만 읽혀 기능이 안된거같아".
- 받은 인자를 파일 로그에 남기게 한 뒤(`켤 때 받은 인자 N개: …`) 다시 해 받아, 떠 있는 프로세스의 명령줄 · 파일 로그 · 폴더 비교 탭 칸을 **읽기만** 했다(업무 폴더의 파일은 열지 않음). 탐색기는 두 폴더를 모두 넘겼고 `StartupArgs` 도 왼쪽 · 오른쪽을 맞게 잡았다. 그런데 **왼쪽 칸이 설정의 기본 저장소 `C:/Users/Ubisam/AppData/Local/Temp/plain-compare`** 였고 결과가 "왼쪽만" 투성이였다.
- 원인: `FolderViewModel` 생성자가 넘어온 요청을 먼저 받아 비교를 시작한 **뒤에** "열어 둔 저장소를 왼쪽 기본값으로" 가 왼쪽을 덮어썼다. 비교는 비동기라 덮인 왼쪽을 읽을 수 있다. 위 시험(`f3-e2e.ps1`)에서는 로그가 맞게 나와 지나쳤다 — 칸 값을 읽는 필터가 기대 경로만 골라 덮인 왼쪽 칸이 안 보였다(시험이 모자랐다).
- 고침: 기본값을 먼저 두고 요청을 나중에 받는다 — 요청이 늘 이긴다. 폴더 하나만 보낸 경우도 같이 고쳐진다.
- 확인(`f3-fix.ps1`, 기본 저장소 설정 그대로): 폴더 2개 → 칸 `…\Left` · `…\Right`, 요약 `전체 3개 · 다름 1 · 왼쪽만 1 · 같음 1`(재료와 같음). 폴더 1개 → 왼쪽 칸 = 보낸 폴더, 오른쪽 빈 칸 + "오른쪽 폴더를 고르고 비교를 누르세요".
- 켤 때 받은 인자를 파일 로그(`D:\UbisamConfig\Log`)에 남기는 한 줄은 그대로 둔다 — 다음에 같은 말이 나오면 무엇이 넘어왔는지 바로 안다. 커밋 `c35280a`.
- 사용자가 같은 두 폴더로 다시 보내기 → "잘되네"(2026-09-22).
### 남은 것
- 이미 떠 있는 창으로 보내기(한 번만 실행 · 창 사이 전달) — 플랫폼 일이라 이번 범위 밖.
- 260자 넘는 경로를 여는 것 — 플랫폼 실행 파일의 긴 경로 설정과 전체 시험이 필요.
- 여러 개를 골랐을 때 **어느 것이 왼쪽인지**는 탐색기가 넘기는 순서를 따른다(도구는 고른 순서대로 넘겼다). 실제 탐색기 창에서 누른 순서와의 관계는 재지 않았다.
## F-4. 툴박스(ACTIONS) 감추기 + 아래 탭 줄 접기 (2026-09-22, 완료 — 플랫폼 배포함)
사용자: F-4 에 더해 "밑에 탭화면도 접을 수 있게 해줘 머지 메인 화면이 더 많이 나와야해".
### 먼저 본 것
- 파일 비교 화면(HomeView) 안에는 아래쪽 탭이 없다. 화면에서 아래에 있는 것은 **플랫폼 창의 탭 줄**(`NavList` — 파일 비교 · Git · 폴더 탭과 화면 잠금 · 바탕화면 · 종료 동작 항목), 1440p 150% 에서 높이 95px. 플랫폼에는 탭 줄 위치(아래 · 왼쪽 · 오른쪽) 설정은 있었지만 접는 기능은 없었다.
- ACTIONS 칸은 "접기" 가 있어도 폭은 그대로였다(4차 C-5).
### 정한 것 (2026-09-22 사용자)
탭 줄은 **얇은 띠 + 펴기 단추 + 키**, **아래 탭 줄만**(위 로고 · 시계 줄은 창 손잡이라 둠), 플랫폼에 넣되 **모든 소비 프로그램이 쓰게 · 기본은 펼침**. ACTIONS 는 **단추 + F9**.
### 만든 것
- **플랫폼**: 탭 줄 끝 동작 항목 **[탭 접기]**(새 아이콘), 접으면 16 DIP 띠 `탭 펴기 · F11`, **F11** 로 접고 폄(잠금 중 제외), 상태는 `UiSettingsService.NavCollapsed` — 그 항목만 파일에 고쳐 씀(`UiSettingsFile`). 왼쪽 · 오른쪽 탭 줄이면 세로 띠. 전체 단추 스타일의 `MinHeight 38` 때문에 띠가 처음엔 57px 로 두꺼워 최소 크기를 0 으로.
- **MergeHub**: 위 상태 바에 **[ACTIONS 감추기 ▶ (F9)]** / **[◀ ACTIONS 보이기 (F9)]**, 단축키 표에 F9(도움말에도 나옴). 감추면 ACTIONS 칸과 앞 구분선 폭을 0 으로 해 코드 칸이 넓어지고, 펴면 **끌어 둔 폭 그대로** 돌아온다(폭을 Style 로 쥐지 않는 까닭 — 구분선을 끌면 로컬 값이 박힌다). 상태는 설정 `ActionsHidden`(설정 화면에는 안 나옴).
### 확인한 것
- 하네스: PlatformCfgTest +7(31건 — 옛 파일 · 망가진 글은 펼침, 접기 항목만 바뀌고 다른 값 · 모르는 항목 그대로), ShortcutTest +3(F9 = ACTIONS, F11 은 머지툴 표에 없음). MergeHub 23벌 **1,414건** · 빌드 경고 0 · 오류 0(플랫폼 Debug · MergeHub Debug).
- 화면(`f4-verify.ps1` — MergeHub Debug 를 임시 폴더에 복사하고 플랫폼 Core · Shell 만 고친 Debug 로 바꿔 띄움, 배포 폴더 · 사용자 Debug 폴더 안 건드림, 화면 설정 파일은 바이트 그대로 되돌림):
| 한 것 | 결과(1440p · 150%) |
|---|---|
| [ACTIONS 감추기] | 코드 칸 두 칸이 각각 **+163~164px**(1083 → 1247 · 985 → 1148), 단추 글 `◀ ACTIONS 보이기 (F9)` |
| 감춘 채 Alt+↓ · Alt+→ · Ctrl+S | 차이로 가서 반영하고 저장됨(파일에 반영 줄 · 주석) |
| F9 | 원래 폭으로 |
| [탭 접기] · F11 | 탭 줄 95px → 띠 24px, 코드 칸 **+71px**(1198 → 1269, 약 2줄). 설정 파일에 `"NavCollapsed": true` 한 항목만 더해지고 나머지 그대로 |
| 띠 [탭 펴기] · F11 다시 | 원래대로 |
| 둘 다 감추고 다시 켬 | 두 상태 모두 남음 |
| 탭 줄 왼쪽 | 세로 띠 24px 로 접히고 폄. 왼쪽 탭 줄에서 코드 칸 자리는 고친 플랫폼 · 지금 플랫폼이 같다 |
### 남은 것
- ~~플랫폼 배포~~ — 사용자 "배포해줘" 뒤 `publish-platform.ps1`(16:22, 경고 0 · 오류 0, 48개 가운데 Core · Shell · README 셋 바뀜). MergeHub Debug 를 다시 빌드해 두 파일 해시가 배포본과 같음, 배포본으로 [탭 접기] → 24px 띠 → 띠로 펴기 확인(`f4-deployed.ps1`, 화면 설정 · MergeHub 설정 바이트 그대로 되돌림).
- F-6(넓게 보기)에서 몇 줄이 늘었는지 1920 × 1080 기준으로 다시 잰다.
## F-12. C# 코드 스타일 맞춤의 기본값을 AURORA 로 (2026-09-18, 완료)
사용자: "기본값은 원본형태를 따르고 C#인 경우에 AURORA 스타일로 하자" → 물어서 정한 것은 **제품 기본값 AURORA · C# 파일만 · 저장 동작은 지금 그대로**(켜져 있으면 저장도 그 스타일로 쓴다).
### 알아본 것
- C/C++ · XAML 은 고칠 것이 없었다. 스타일 맞춤은 `CSharpStyle.Applies` 가 **확장자 `.cs` 만** 보므로(`Services/CSharpStyle.cs:60`) 설정이 무엇이든 다른 파일은 원본 그대로 견준다.
- 그래서 바뀐 것은 설정 기본값 한 줄뿐이다.
### 고친 것
| 자리 | 고친 것 |
|---|---|
| `MergeHubSettings.cs:77` | `CSharpBraceStyle` 기본값 `None` → **`Aurora`**. 왜 그렇게 정했는지와 "C# 파일에만 걸린다" 를 주석으로 남김 |
| 웹 문서 매뉴얼 | "기본은 끔" → "**기본은 AURORA**(2026-09-18 결정) · 끄려면 콤보에서 [C# 스타일 끔]" · "C# 파일에만 걸린다" 한 줄 더함 |
| 웹 문서 설정 표 | 기본값 칸 `끔` → `AURORA`, "`.cs` 파일에만 걸린다" |
| 웹 문서 시연 대본 | "스타일 맞춤은 끔으로 둔다" → "기본이 AURORA 이므로 시연 전에 [C# 스타일 끔] 으로 바꿔 둔다" |
| 하네스 `StyleTest` | **+12건(78 → 90)** — 설정 기본값이 AURORA 인지, `.cpp` · `.h` · `.hpp` · `.c` · `.xaml` · `.py` · `.txt` · `.cs.txt` 는 안 걸리는지, `.cs` · `.CS` · 한글 경로 `.cs` 는 걸리는지. 고치기 전에 먼저 넣어 **기본값 검사만 실패**하는 것을 보고 시작했다 |
### 확인한 것 — 설정 파일이 없는 첫 실행 (`e11-f12.ps1`)
설정 파일을 백업해 치우고, 중괄호 자리만 다른 짝 두 벌을 명령줄(`--diff`)로 열었다.
| 무엇 | 결과 |
|---|---|
| C# 짝(`left.cs` Allman · `right.cs` AURORA) | 요약 **"차이 없음"** · 상태바 `무시: 머지 주석 · C# AURORA` (`e11-f12-cs.png`) |
| C++ 짝(`left.cpp` · `right.cpp`, 같은 모양의 중괄호 차이) | 요약 **"남은 차이 4줄"** — 원본 그대로 (`e11-f12-cpp.png`) |
| 앱이 새로 만든 설정 파일 | `"CSharpBraceStyle": 1` (= Aurora) |
| 설정 파일 되돌림 | 해시 같음 |
- 하네스 22벌 **1,159건** 통과(1,147 → +12) · 빌드 경고 0 · 오류 0.
- 이미 설정 파일이 있는 PC 는 그 값을 그대로 쓴다. 이번 변경은 **설정이 없는 첫 실행**에만 닿는다.