3단계 계속.
<b>① Ctrl+S 가 DB 문서를 디스크에 쓰고 있었다.</b>
Ctrl+S 는 SaveFileCommand 에 걸려 있고 그건 항상 파일 저장이다.
DB 에서 연 서식에서 누르면 OS 저장 대화상자가 뜨고 XML 이 디스크에 쓰였다 —
사용자는 저장했다고 믿지만 <b>DB 에는 아무것도 안 들어간다</b>.
종료 가드는 이미 IsFromDb 로 갈라 준다(MainViewModel.cs:365). Ctrl+S 만 안 갈라 주고 있었다.
<b>② 저장 완료 알림 모달을 없앤다.</b> 정보량이 0 이었다 —
SdgKey 를 바로 위에서 상태바에 넣고 같은 값을 모달로 한 번 더 보여 주는 것이었다.
하루 30회 저장하는 사람에게 그건 Enter 를 30번 치는 의식이고, 그때마다 화면이 어두워졌다.
실패는 계속 모달로 알린다 — 조용히 넘기면 저장된 줄 알고 넘어간다.
<b>③ 스크림의 기본을 뒤집었다.</b> 전에는 모든 ShowDialog 에 무조건 붙어서
태그 하나 고르는 데도 앱이 암전했다. 하루 수백 번이면 신호가 아니라 소음이다.
전역 후크는 그대로 둔다 — 호출부 17곳 중 하나만 빠져도 그 대화상자만 다르게 동작하고
그건 눈으로 전수 확인해야만 안다. 대신 창이 스스로 자격을 밝혀야 깔리게 했다
(ModalScrim.DimBehind). 새 대화상자가 잊으면 암전이 안 되는데, 이제 그쪽이 안전한 기본값이다.
자격을 밝힌 것: 물음(되돌릴 수 없는 쓰기의 확인)·오류(진행이 정의되지 않는 차단)·서식 신규 등록.
--modal-check 에 ⑤ 를 더했다: <b>자격을 밝히지 않은 창은 어두워지지 않는다</b>.
①~④ 만으로는 "깔린다"만 확인되고 "안 깔려야 할 때 안 깔린다"는 확인되지 않는다.
<b>④ 배치 명령에 단축키와 우클릭 메뉴를 붙였다.</b>
전에는 단축키가 0개였고 우클릭 메뉴가 앱 전체에 하나도 없었다.
정렬 6종·같은 크기·간격이 전부 로고 아래 4단 메뉴 안에만 있어서,
20px→32px 간격 조정이 4px 씩 12왕복이고 매번 마우스가 화면 좌상단까지 갔다.
자리는 Figma 와 같게 둔다(Alt+A/D/W/S/H/V) — 이미 그 손버릇을 가진 사람이 있다.
균등 Alt+Shift+H/V, 같은 크기 Alt+Shift+W/S, 간격 Alt+[ / Alt+].
<b>Alt 조합에 함정이 있다.</b> WPF 는 Alt+문자를 Key.System 으로 싸서 넘기고
실제 키는 SystemKey 에 있다. e.Key 만 보면 Alt 단축키가 하나도 안 걸리는데,
그 실패는 "눌러도 아무 일이 없다"로만 보여서 원인을 찾기 어렵다.
우클릭 메뉴에 InputGestureText 를 적었다 — 단축키를 모르는 사람이 거기서 배운다.
게이트: 테스트 285/285, --edit-smoke 0실패, --modal-check 0실패(신규 1),
--dialog-shots 넘침 0(대조군 4/4), --scale-budget 5/5, --maxrect 0실패,
--cleartype 11/11, 빌드 경고 0,
--db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
두 번째 오진이다. 지난번에는 가림막이 아예 안 깔렸고, 고친 뒤에는 <b>대화상자 위에</b> 깔렸다.
그래서 대화상자까지 같이 흐려져 글자가 눌려 보였다.
원인: 가림막과 대화상자가 같은 창을 소유자로 갖는 형제라, 나중에 띄운 가림막이 위에 온다.
Activate() 로는 안 된다 — 활성 창을 바꾸는 것과 z순서를 바꾸는 것은 다른 일이다.
SetWindowPos 로 가림막을 대화상자 바로 아래에 꽂는다.
두 번 다 "코드를 읽어서는 맞아 보이는데 화면은 다른" 경우였다.
그래서 --modal-check 에 z순서 검사를 더했다 — 창 개수만 세면 위에 있는지 아래에 있는지 모른다.
GetWindow(GW_HWNDNEXT) 로 대화상자 다음 창이 가림막인지 본다.
실측: 떠 있는 동안 가림막 1개 · 대화상자 아래에 있음 · 닫으면 0개.
게이트: 테스트 277/277, --edit-smoke 0실패, --modal-check 3/3(1건 신규),
--dialog-shots 57파일, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
직전 커밋(d9c1eaa)은 코드만 멀쩡했고 화면에는 아무 일도 일어나지 않았다.
ShowDialog() 는 창을 먼저 보이고(그때 Loaded 가 온다) <b>그다음에</b> 모달 루프에 들어간다.
그래서 Loaded 에서 읽는 ComponentDispatcher.IsThreadModal 은 아직 false 다 —
가드에 걸려 매번 그냥 반환했다. 모달 프레임이 돌기 시작한 뒤로 판정을 미룬다.
한 프레임 늦게 깔리는 것은 눈에 띄지 않는다.
이런 종류는 코드를 다시 읽어서는 안 잡힌다. 열어 봐야 안다.
그래서 --modal-check 를 만들었다: 대화상자를 실제로 ShowDialog 로 띄우고
그 순간 가림막 창이 화면에 존재하는지 센 뒤 닫는다.
이 진단을 만들면서 두 번 헛다리를 짚었고, 둘 다 검사가 스스로를 속인 경우다.
· BeginInvoke 로 세면 그 호출이 Loaded 보다 먼저 큐에 들어가 가림막을 거는 콜백보다
앞서 실행된다 — 항상 0 이 나온다. 타이머로 바꿨다.
· 진단 분기는 OnStartup 의 전역 배선보다 먼저 반환하므로 진단 모드에는 그 훅이 없다.
검사가 같은 배선을 직접 걸어야 프로덕션과 같은 것을 잰다.
실측: Loaded 시점 IsThreadModal=False / 모달 루프 안 True / 떠 있는 동안 가림막 1개 / 닫으면 0개.
게이트: 테스트 267/267, --edit-smoke 0실패, --maxrect 10/10, --dialog-shots 57파일,
--db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일, --modal-check 2/2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WPF 의 ShowDialog 는 입력만 막고 화면은 그대로 둔다. 대화상자가 앱과 같은 밝기로 떠 있으면
"이게 떠 있는 동안은 뒤를 못 만진다"가 안 읽혀서, 뒤를 클릭했다가 아무 반응이 없으면
멈춘 것으로 오해한다.
창이 뜨는 순간을 전역으로 잡아 모달일 때만 반투명 검정 창을 뒤에 깐다.
호출부마다 고치지 않은 이유는 ShowDialog 가 17곳이고 앞으로 더 늘기 때문이다 —
한 곳이라도 빠지면 그 대화상자만 다르게 동작하는데 그건 눈으로 전수 확인해야만 알 수 있다.
제목 표시줄 색을 거는 자리(WindowChromeTheme)와 같은 곳에 나란히 걸었다.
모달 판정은 ComponentDispatcher.IsThreadModal 이다. 진단 모드는 창을 모달이 아니라
화면 밖에 그냥 띄우므로 이 경로를 안 탄다 — --dialog-shots 57파일 그대로다.
가림막은 뒤 창의 이동·크기·상태 변화를 따라간다. 안 따라가면 창을 옮겼을 때
어두운 사각형만 제자리에 남는다. 최대화된 창은 Left/Top 이 '복원했을 때의 값'이라
그대로 쓰면 엉뚱한 데 깔리므로 화면 좌표를 직접 물어 DIP 로 되돌린다.
대화상자가 겹쳐 뜨면 그만큼 쌓이고, 각자 닫힐 때 제 것만 걷는다.
게이트: 테스트 267/267, --edit-smoke 0실패, --query-popup 17/17, --maxrect 10/10,
--db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일,
--dialog-shots 57파일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>