diff --git a/doc/HANDOFF.md b/doc/HANDOFF.md new file mode 100644 index 0000000..1cd3e83 --- /dev/null +++ b/doc/HANDOFF.md @@ -0,0 +1,250 @@ +# 인수인계 — MergeHub (2026-09-30 세션 끝) + +다음 세션(다른 AI 또는 사람)이 **먼저 읽는** 문서. "어디에 무엇이 있는지" 보다 +**"왜 그렇게 되어 있고, 무엇을 건드리면 깨지는지"** 를 적는다. + +| 문서 | 내용 | +| --- | --- | +| `doc/진행상황.md` | 차수별 항목과 상태 — 무엇이 남았는지 | +| `doc/history/2026-09-23-12차.md` | 12차 진행 기록 — 항목마다 재현·고침·확인값 | +| `doc/history/changelog.md` | 변경 이력, **최신이 위** | +| `doc/작업지시/12차.md` | 이번 차수 지시서 | + +--- + +## 1. 지켜야 할 것 (어기면 사고) + +- **커밋은 로컬만. 절대 푸시하지 않는다.** 올리는 것은 사용자가 한다. +- 커밋 메시지 끝에 `Co-Authored-By: Claude Opus 5 `, + 그 위에 `Agent: 이름 (모델)`. +- **실제 업무 저장소를 읽지 않는다.** 시험 재료는 만들어 쓴다. +- **화면 시험 전에 떠 있는 창을 확인하고, 사용자 창을 끄기 전에 묻는다.** +- 웹 문서는 **차수 끝에 한 번에** — 차수 중간에는 고치거나 올리지 않는다. + (11차 F-1~F-11 + G-1 + 12차 전체가 **아직 밀려 있다.**) +- `D:\UbisamConfig` 아래는 하네스가 `Remove-Item` 을 막는다 → + `[IO.File]::Delete($f)` 로 지운다. +- `HKLM` 쓰기 · 셸 확장 설치 · 보안/백신/디스크/디스플레이 설정 변경은 하지 않는다. + +### 설정 파일은 바이트까지 되돌린다 + +화면 시험은 사용자의 설정을 건드린다. 시험 뒤 **반드시** 아래 넷을 시작 기준본과 +바이트까지 같게 되돌린다(기준본은 scratchpad 의 `h8-*.bak`). + +``` +D:\UbisamConfig\Settings\MergeHubSettings.json +D:\UbisamConfig\Settings\UiSettingsService.json +D:\UbisamConfig\Settings\MergeHub.Recent.txt ← Settings 아래다. 헷갈리기 쉽다 +D:\UbisamConfig\Settings\ShellWindowPlacement.json +``` + +보관함도 비운다: `D:\UbisamConfig\Memo`, `D:\UbisamConfig\Session\MergeHub`. + +--- + +## 2. 항목 하나를 끝내는 순서 + +이 순서를 줄이지 않는다. 특히 **먼저 재현** 을 건너뛰면 엉뚱한 것을 고친다 +(12차 G-1 에서 두 번 헛짚었다 — 아래 6절). + +``` +1) 먼저 재현한다 — 값으로. "그럴 것이다" 로 시작하지 않는다 +2) 하네스를 먼저 만든다 — 순수 로직은 scratchpad 의 net8 콘솔 프로젝트로 +3) 빌드 0 경고 · 0 오류 — 경고가 나면 고치고 넘어간다 +4) 화면으로 확인(UIA) — 값을 찍는다. "보인다" 가 아니라 "몇 px" 로 +5) 세 문서에 적는다 — 차수 기록 · 진행상황 · changelog +6) 로컬 커밋 — 담당 단위로 나눠서 +``` + +**검증 안 된 동작 변경은 남기지 않는다.** 만들어 봤는데 증상이 안 없어지면 되돌린다 +(G-1 에서 `EditorUndoGate` 와 재정렬 타이머 고침을 둘 다 되돌렸다). + +--- + +## 3. 시험 도구 + +### 자리 + +``` +C:\Users\Ubisam\AppData\Local\Temp\claude\C--Users-Ubisam\8f71cefa-dbf5-4aa8-bcb8-fdb89ccc518f\scratchpad +``` + +**새 세션은 scratchpad 경로가 달라진다.** 위 폴더는 디스크에 그대로 남아 있으니, +새 세션에서 쓰려면 **그 경로를 직접 가리키거나 복사해 온다.** 안에 든 것: + +| 무엇 | 것 | +|---|---| +| 공용 라이브러리 | `e2fold.ps1`(재료 만들기 · 폴더 비교 · 앱 띄우기), `e2lib.ps1`(`Editor-Text` · `Set-Editor` · `Dialog-Window`), `a2lib.ps1`(프로세스 · 창 잡기) | +| 화면 시험 벌 | `g1-verify` `g2-verify` `f1-verify` `d1-verify` `e1-verify` `e2-verify` `e2-reg` `e2-start` `e3-verify` `e4-verify` `c3-verify` | +| 몰이 대본 | `run-all-merge.ps1`(아홉 벌 차례로 · 앞 벌 뒤처리까지) | +| 하네스 | `EditorTest` `ShortcutTest` `DocTabTest` `MemoTest` `FolderTest` `SessionTest` `PlatformCfgTest` 등 31개 | +| 설정 기준본 | `h8-*.bak` (세션 시작 상태) | + +하네스는 net8 콘솔이고 `.csproj` 에서 MergeHub 의 `.cs` 를 **직접 가리켜** 컴파일한다. +새 파일을 시험하려면 그 `.csproj` 에 `` 를 더한다. + +### 반드시 아는 함정 + +- **`.ps1` 은 UTF-8 BOM 으로 저장한다.** Write 도구나 bash 히어독은 BOM 을 안 붙이고, + PowerShell 5.1 은 그러면 한글을 ANSI 로 읽어 **문법 오류로 죽는다.** 쓴 뒤 붙일 것. +- **bash 히어독은 역슬래시를 먹는다.** Python 편집 대본은 Write 도구로 만들고 + **raw 문자열**(`r'...'`)을 쓴다. +- **빌드 전에 시험용 앱을 반드시 끈다.** 떠 있으면 dll 이 잠겨 빌드가 조용히 실패하고, + **옛 빌드로 시험하게 된다**(실제로 겪었다). +- PowerShell 함수 이름이 기본 별칭과 부딪힌다 — `Type` 은 `Get-Content` 다. `Send-Text` 처럼 짓는다. +- 하네스 변수 `$keep` 은 라이브러리 `$Keep` 을 가린다. 다른 이름을 쓴다. + +### 흉내 낸 입력으로 못 하는 것 (도구 한계 · 사람 손이 필요) + +| 무엇 | 왜 | +|---|---| +| `Alt+Shift+←` · `Alt+Shift+→` | 윈도우가 **입력 언어 전환**으로 먼저 채간다. `SendKeys` 는 아예 안 닿고 `keybd_event` 는 Shift 가 빠져 `Alt+←` 처럼 동작한다. **기존 `Alt+Shift+→` 로 견줘도 같아** 도구 한계임을 확인했다 → **단추로 우회해 확인한다** | +| 창 끌기(`DragMove`) | 흉내 낸 마우스로는 WPF 가 안 움직인다. 기존 헤더 끌기로 견줘도 같다 | +| 큰 파일 타이핑 지연 | UIA 로 읽는 값이 타이핑보다 비싸 잴 수 없다 | + +UIA 참고: `Border` 와 맨 `ItemsControl` 은 컨트롤로 안 잡힌다(`ListBox` 는 잡힌다). + +--- + +## 4. 저장소 · 배포 지금 상태 + +| 무엇 | 자리 | 상태 | +|---|---|---| +| MergeHub | `D:\Sorce\MergeTool\MergeHub` | 깨끗 · HEAD `a86dc15` · **푸시 안 함** | +| 공용 플랫폼 | `D:\Sorce\UbisamCommonPlatform` | 깨끗 · **`028a50b` 한 커밋이 안 올라감**(dist 배포본) | +| 옛 플랫폼 사본 | ~~`D:\Sorce\ubsrepo`~~ | **사용자가 지웠다.** 플랫폼은 공용 저장소에서만 고친다 | +| 배포 폴더 | `D:\UbisamPlatform\bin` | Release 51개 · 되돌릴 자리 `bin-backup-merge` · `bin-backup-c3` | + +**플랫폼은 빌드하는 순간 `D:\UbisamPlatform\bin` 에 반영된다.** 시험 빌드로 덮지 않으려면 +`-p:UbisamPublishDir=<딴 자리>\` 로 돌린다(명령줄 전역 속성이 PropertyGroup 보다 세다). + +**`028a50b` 를 올리면 사내 다른 PC 가 다음 실행 때 업데이트를 묻는다.** 앞의 세 커밋은 +`dist` 를 안 건드려 조용했지만 이것은 다르다. 올리기 전에 Release 빌드 위에서 +화면 회귀를 한 번 더 돌리기를 권했고, 사용자는 아직 답하지 않았다. + +**공용 플랫폼 저장소는 일하는 방식이 다르다** — 그 `CLAUDE.md`·`doc/HANDOFF.md` 가 +"토큰을 아낀다, 앱을 띄워 클릭·캡처하는 검증 루프를 쓰지 않는다, **화면 확인은 사용자에게 +한 줄로 부탁한다**" 고 못 박았다. 거기서는 UIA 회귀를 돌리지 않는다. + +--- + +## 5. 지금 하던 일 (12차 G) + +사용자가 준 다섯 가지 중 **둘은 끝났고 셋이 남았다.** 그 뒤 네 가지가 더 들어왔다. + +### 끝난 것 + +| | | +|---|---| +| **G-1 undo 가 계속 이어진다** | `LocalEditor` 에 `IsUndoEnabled="False"` 한 줄. 커밋 `b95a07d` | +| **G-2 base 머지 단축키** | `Alt+←` 한 줄 왼쪽으로 · `Alt+Shift+←` 구간 왼쪽으로 · `[이 덩어리 왼쪽으로]` 단추. 커밋 `a86dc15` | + +### 남은 것 — 결정은 다 받아 뒀다 + +| # | 무엇 | 사용자가 정한 것 | +|---|---|---| +| **G-3** | "덩어리" 라는 말이 어색하다 | **`구간 반영` · `구간 왼쪽으로`** 로 바꾼다 | +| **G-4** | 저장 형식 칸에 `원본 유지` 콤보가 **둘** 이라 구별이 안 된다 | **글자를 다르게** — 인코딩 쪽과 줄바꿈 쪽 문구를 갈라 적는다 | +| **G-5** | 사내 문의처가 배포본에 안 따라간다 | **`csproj` 에 받침값을 박는다.** ⚠ **문구를 아직 못 받았다 — 물어야 한다** | +| **G-6** | 프로그램 개요 탭 | **읽기 전용 안내 화면**(개요 · 만든사람 · 문의처 · 버전) | +| **G-7** | `Alt+↓` 로 이동하다 보면 화면이 잘린다 | 아직 재현 안 함 | +| **G-8** | 메모 탭에 문법 강조 + 마킹 | **`RichTextBox` 로 바꾸고** 기존 `CSharpTokenizer` 재사용. 마킹 **셋 다**: 고른 글과 같은 글 모두 칠하기 · 색 골라 여러 개 · 오른쪽 끝 위치 막대 | + +### G-3 · G-4 를 고칠 자리 (이미 찾아 뒀다) + +| 파일 | 줄 | 무엇 | +|---|---|---| +| `Views/HomeView.xaml` | 1783 · 1785 | `이 덩어리 반영` 단추 글 · 툴팁 | +| `Views/HomeView.xaml` | 1789 · 1793 | `이 덩어리 왼쪽으로` 단추 글 · 툴팁 | +| `Services/ShortcutMap.cs` | 132 · 142 | 단축키 설명 두 줄 | +| `ViewModels/HomeViewModel.cs` | `ApplyGroup(..., what: "덩어리")` · `ApplyGroupToBase(block, "덩어리")` | 로그 문구에 들어간다 | +| `Services/TextFile.cs` | 57 · 60 · 63 | `KeepOriginal = "원본 유지"` 와 두 목록 | + +### ⚠ G-4 를 고치기 직전에 찾은 것 — 그냥 바꾸면 안 된다 + +`TextFile.KeepOriginal` 은 **화면에 보이는 글이면서 동시에 저장되는 값**이다. + +- `HomeViewModel.SaveEncoding` · `SaveNewLine` 의 기본값이 이 문자열이고, +- 그 값이 **세션 보관본에 그대로 쓰인다** — `SessionStore.cs:165-166`(쓰기), + `:212-213`(읽기), `목록.txt` 의 7·8번째 칸. + +그래서 문자열만 바꾸면 **이미 보관된 세션의 값이 목록에 없는 값이 되어** 되살릴 때 +어긋난다. 갈 수 있는 길: + +1. **보여 주는 글과 저장하는 값을 나눈다** — 목록은 `(값, 보일 글)` 짝으로 하고 + 콤보에 `DisplayMemberPath` 를 준다. 저장되는 것은 값 그대로라 옛 보관본이 산다. (권함) +2. 문자열을 바꾸고 `SessionStore` 읽을 때 옛 값(`"원본 유지"`)을 새 값으로 옮긴다. + +어느 쪽이든 **세션 되살리기 회귀(`e1-verify`)를 반드시 돌린다.** + +--- + +## 6. 12차 G-1 에서 배운 것 (같은 실수 반복 금지) + +`undo 가 계속 이어진다` 를 잡는 데 오래 걸렸다. 두 번 헛짚었다. + +| 짚었던 것 | 어떻게 아니라고 알았나 | +|---|---| +| "문서가 바뀔 때 TextBox 기록을 비우면 된다" — `EditorUndoGate` 를 만들어 하네스 16건까지 붙였다 | 앞 파일로 넘어가는 것은 막았지만 **오가는 것은 그대로**였다 | +| "재정렬 타이머가 되돌린 뒤 뒤늦게 터진다" | 타이머를 꺼도 **그대로**였다 | + +**결국 답을 준 것은 기록(trace)이다.** 코드를 읽어 추리하지 말고, +`Undo` · `Reflow` · `SyncEditorFromLines` 에 한 줄씩 찍어 고리를 떴더니 바로 보였다. +임시 기록은 환경 변수로 켜고(`MERGEHUB_G1_TRACE`) **끝나면 뺐다.** + +교훈 셋: +1. **재현 대본이 고치기 전에 실패하는지 먼저 확인한다.** 첫 `g1-verify` 는 + 고치기 전에도 통과했다 — 재현을 못 하고 있었던 것이다. +2. `Set-Editor`(UIA 로 통째로 넣기)와 **사람이 치는 것**은 기록이 다르다. +3. 증상이 여러 겹이면 **하나씩 떼어** 본다(글자 없이 · 글자 치고). + +--- + +## 7. 코드 지형 (자주 가는 곳) + +``` +src/MergeHub/ +├─ AppSetup.cs 탭 등록(AddMain 순서 = 화면 순서) · ViewIds +├─ MergeHubSettings.cs 설정 항목([DisplayName] 이 곧 설정 화면 글) +├─ Models/MergeLine.cs 줄 하나(BaseText/LocalText/RemoteText · Has* · CanTake*) +├─ Services/ +│ ├─ ShortcutMap.cs 단축키 표 — 키 처리와 도움말 창이 둘 다 이것만 본다 +│ ├─ TextFile.cs 인코딩 · 줄바꿈 · 저장 +│ ├─ DocTabs.cs 문서 탭(12차 E-2) · 보관함(attic) +│ ├─ SessionStore.cs 껐다 켜도 남는 편집(12차 E-1) +│ ├─ Memos.cs · MemoStore 메모 탭(12차 F-1) +│ └─ CodeTokenizer.cs 문법 강조 — 메모에 쓸 재료 +├─ ViewModels/ +│ ├─ HomeViewModel.cs 4천 줄. 줄 편집 · 되돌리기 · 저장 +│ ├─ HomeViewModel.Tabs.cs 문서 탭 · 세션 +│ └─ MemoViewModel.cs +└─ Views/ + ├─ HomeView.xaml(.cs) 파일 비교. LocalEditor 는 맨 TextBox(글자 투명 + 아래층에 색칠) + ├─ Controls/CodeTextBlock.cs · CSharpTokenizer.cs 문법 강조(보여주기 전용) + └─ MemoView.xaml(.cs) 메모 탭. 지금은 맨 TextBox +``` + +### 꼭 아는 것 + +- **되돌리기는 두 겹이다** — `_undo`(줄 단위 `LineEdit`)와 `_textUndo`(글 전문, 타이핑이 + 멈출 때마다 한 걸음). `FlattenHistory()` 가 줄 단위를 글 단위로 옮긴다. + `ClearHistory()` 는 **저장 직후에도 불린다** — "파일이 바뀌었다" 의 신호로 쓰면 안 된다. +- **덩어리 명령은 고른 줄이 아니라 `CaretRow`(편집기 캐럿)를 본다.** +- **CommunityToolkit 커맨드는 `CommandManager` 를 구독하지 않는다.** 새 명령을 만들면 + `RaiseCanExecuteChanged()` 와 (캐럿을 보는 것이면) `OnCaretRowChanged` 에 **꼭 넣는다.** + 안 넣으면 단추가 꺼진 채로 남는다(G-2 에서 겪었다). +- 편집기 글자는 **투명**이다. 색은 아래층이 그린다 — 그래서 "한참 뒤에 반응한다" 가 + 곧 "아무것도 안 보인다" 가 된다. +- 만든 사람은 설정이 비면 `csproj` 의 `` 로 받쳐지지만(`ProgramInfo.Prefer`), + **문의처는 받칠 것이 없다**(`Prefer(contact, null)`) — 그래서 G-5 가 나왔다. + +--- + +## 8. 사용자와 일하는 방식 + +- **한국어로 답한다.** 코드 주석도 한국어이고 "무엇을" 이 아니라 **"왜"** 를 적는다. +- 사용자는 항목을 하나씩 준다(`X 진행해줘`). 여러 개를 한꺼번에 주면 **순서를 제안하고** + 하나씩 끝낸다. +- 갈리는 결정(문구 · 단축키 · 화면 모양)은 **묻는다.** 사용자가 골라 주면 그대로 간다. +- **추측으로 원인을 말하지 않는다.** 값으로 확인한 뒤 말하고, 못 했으면 + **"값으로 확정하지 못했다"** 고 분명히 적는다(문서와 커밋 메시지 양쪽에). +- 헛짚은 것도 기록에 남긴다 — 다음 사람이 같은 길을 또 파지 않게. diff --git a/doc/진행상황.md b/doc/진행상황.md index 0fff0c0..48d2151 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -1,5 +1,8 @@ # MergeHub 진행 상황 +> **이어받는 사람은 [HANDOFF.md](HANDOFF.md) 를 먼저 읽는다** — 지켜야 할 것, +> 항목 하나를 끝내는 순서, 시험 도구가 있는 자리와 함정, 지금 하던 일이 적혀 있다. + **지금 무엇이 남았는지만** 적는다. 끝난 것의 상세는 [history/](history/) 로 옮겼다. 항목을 끝내면 이렇게 한다.