세션을 새로 여는 참에, 다음 사람이 맨땅에서 시작하지 않게 정리한다. "어디에 무엇이 있는지" 보다 "왜 그렇게 되어 있고 무엇을 건드리면 깨지는지" 를 적었다. 진행상황.md 맨 위에서 이 문서를 가리키게 했다. 담은 것 - 지켜야 할 것: 푸시 금지, 설정 파일을 바이트까지 되돌리기, D:\UbisamConfig 아래는 Remove-Item 이 막히니 IO.File.Delete 를 쓸 것 - 항목 하나를 끝내는 순서(재현 -> 하네스 -> 빌드 0/0 -> 화면 -> 세 문서 -> 커밋)와 "검증 안 된 동작 변경은 남기지 않는다" - 시험 도구가 있는 자리. 새 세션은 scratchpad 경로가 달라지므로 지금 경로를 그대로 적어 뒀다. 함정도 함께 - .ps1 은 BOM 필요, bash 히어독은 역슬래시를 먹음, 빌드 전에 시험용 앱을 꺼야 함(안 그러면 옛 빌드로 시험하게 된다) - 흉내 낸 입력으로 못 하는 것: Alt+Shift 조합(윈도우가 입력 언어 전환으로 채감), 창 끌기(DragMove), 큰 파일 타이핑 지연. 셋 다 사람 손이 필요하다 - 저장소·배포 지금 상태. 공용 플랫폼에 dist 커밋 하나(028a50b)가 안 올라가 있고, 그것을 올리면 사내 다른 PC 가 업데이트를 묻는다는 것까지 - 12차 G 의 남은 여섯 항목과 사용자가 이미 정해 준 결정 - G-1 에서 두 번 헛짚은 기록과 교훈(추리하지 말고 기록을 찍어라) - 코드 지형과 자주 걸리는 것들(되돌리기 두 겹, 덩어리 명령은 CaretRow 를 본다, CommunityToolkit 커맨드는 RaiseCanExecuteChanged 에 넣어야 단추가 켜진다) G-4 를 고치기 직전에 찾은 것도 적어 뒀다. TextFile.KeepOriginal 은 화면에 보이는 글이면서 동시에 세션 보관본에 저장되는 값이라(SessionStore 목록.txt 7·8번째 칸), 문자열만 바꾸면 이미 보관된 세션이 어긋난다. 보여 주는 글과 저장하는 값을 나누는 쪽을 권해 뒀다. Agent: main (claude-opus-5) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
14 KiB
인수인계 — 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 <noreply@anthropic.com>, 그 위에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 에 <Compile Include="D:\Sorce\...\X.cs" /> 를 더한다.
반드시 아는 함정
.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번째 칸.
그래서 문자열만 바꾸면 이미 보관된 세션의 값이 목록에 없는 값이 되어 되살릴 때 어긋난다. 갈 수 있는 길:
- 보여 주는 글과 저장하는 값을 나눈다 — 목록은
(값, 보일 글)짝으로 하고 콤보에DisplayMemberPath를 준다. 저장되는 것은 값 그대로라 옛 보관본이 산다. (권함) - 문자열을 바꾸고
SessionStore읽을 때 옛 값("원본 유지")을 새 값으로 옮긴다.
어느 쪽이든 세션 되살리기 회귀(e1-verify)를 반드시 돌린다.
6. 12차 G-1 에서 배운 것 (같은 실수 반복 금지)
undo 가 계속 이어진다 를 잡는 데 오래 걸렸다. 두 번 헛짚었다.
| 짚었던 것 | 어떻게 아니라고 알았나 |
|---|---|
"문서가 바뀔 때 TextBox 기록을 비우면 된다" — EditorUndoGate 를 만들어 하네스 16건까지 붙였다 |
앞 파일로 넘어가는 것은 막았지만 오가는 것은 그대로였다 |
| "재정렬 타이머가 되돌린 뒤 뒤늦게 터진다" | 타이머를 꺼도 그대로였다 |
결국 답을 준 것은 기록(trace)이다. 코드를 읽어 추리하지 말고,
Undo · Reflow · SyncEditorFromLines 에 한 줄씩 찍어 고리를 떴더니 바로 보였다.
임시 기록은 환경 변수로 켜고(MERGEHUB_G1_TRACE) 끝나면 뺐다.
교훈 셋:
- 재현 대본이 고치기 전에 실패하는지 먼저 확인한다. 첫
g1-verify는 고치기 전에도 통과했다 — 재현을 못 하고 있었던 것이다. Set-Editor(UIA 로 통째로 넣기)와 사람이 치는 것은 기록이 다르다.- 증상이 여러 겹이면 하나씩 떼어 본다(글자 없이 · 글자 치고).
7. 코드 지형 (자주 가는 곳)
src/MergeHub/
├─ AppSetup.cs 탭 등록(AddMain 순서 = 화면 순서) · ViewIds
├─ MergeHubSettings.cs 설정 항목([DisplayName] 이 곧 설정 화면 글)
├─ Models/MergeLine.cs 줄 하나(BaseText/LocalText/RemoteText · Has* · CanTake*)
├─ Services/
│ ├─ ShortcutMap.cs 단축키 표 — 키 처리와 도움말 창이 <b>둘 다 이것만</b> 본다
│ ├─ 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의<Company>로 받쳐지지만(ProgramInfo.Prefer), 문의처는 받칠 것이 없다(Prefer(contact, null)) — 그래서 G-5 가 나왔다.
8. 사용자와 일하는 방식
- 한국어로 답한다. 코드 주석도 한국어이고 "무엇을" 이 아니라 "왜" 를 적는다.
- 사용자는 항목을 하나씩 준다(
X 진행해줘). 여러 개를 한꺼번에 주면 순서를 제안하고 하나씩 끝낸다. - 갈리는 결정(문구 · 단축키 · 화면 모양)은 묻는다. 사용자가 골라 주면 그대로 간다.
- 추측으로 원인을 말하지 않는다. 값으로 확인한 뒤 말하고, 못 했으면 "값으로 확정하지 못했다" 고 분명히 적는다(문서와 커밋 메시지 양쪽에).
- 헛짚은 것도 기록에 남긴다 — 다음 사람이 같은 길을 또 파지 않게.