fccd4411932f2e3083ccb2237c9107b921806b35
68
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d6052610b9 |
루프를 키보드로 닫는다 — Tab 다음, Shift+Tab 이전, Enter 안, Esc 밖
이 도구의 일은 "컨트롤 하나 골라 값 하나 바꾸고 다음으로"의 반복인데, <b>그 루프가 마우스 없이는 한 바퀴도 돌지 않았다.</b> 선택을 옮길 키가 하나도 없었다 — 캔버스 키맵 18개 중 선택 이동 0, 편집 시작 0. 화살표는 선택이 아니라 컨트롤을 민다. Label 59,997 + TextBox 28,574 의 문구를 고치는 일이 전부 더블클릭에 묶여 있었다. <b>읽는 순서로 돈다 — z-order 가 아니다.</b> 계획서는 z-order 순회를 적었지만, Tab 을 누르는 이유는 "서식을 위에서 아래로 훑으며 문구를 고치는 것"이다. 기록지는 위→아래·왼쪽→오른쪽으로 읽으므로 그 순서가 손에 맞는다. z-order 는 겹침을 정하는 축이라 화면상 순서와 무관하게 튄다. 같은 줄로 볼 만큼 가까우면(8px = 그리드 두 칸) X 로 가른다. EMR 런타임 입력 순서인 ApplyTabOrder 와는 다른 개념이다 — 그건 저장되는 값이다. <b>범위는 형제다.</b> 컨테이너 안으로는 Enter, 밖으로는 Esc. 전체를 한 줄로 이으면 패널 안 라디오 15,026개를 지나야 다음 항목에 닿는다. Esc 가 이제 한 겹만 나간다. 전에는 항상 선택을 통째로 풀어서, 패널 자식을 고른 뒤 Esc 를 누르면 부모로 돌아가는 대신 선택이 사라졌다. 드래그 중 Esc 는 그대로 드래그 취소다. 선택이 옮겨지면 RevealControl 로 화면에 데려온다 — 레이어 목록 화살표 이동에 이것이 없어서 화면 밖 컨트롤을 눈 없이 편집할 수 있었다. PreviewKeyDown 에 붙인다. KeyDown 이면 WPF 포커스 관리자가 Tab 을 먼저 먹어 포커스가 캔버스 밖(좌측 패널·문서 탭·플로팅 바)으로 나간다. 검사는 배선이 아니라 <b>한 바퀴가 도는지</b>를 본다 — 9건: 읽는 순서 · 끝에서 처음으로 되돌아옴 · 역방향 · 컨테이너 안 · 밖 · 나갈 곳 없음 · 편집 시작. 컨테이너는 GroupSelection 이 아니라 문서에 직접 만든다 — 그 경로에는 거절 조건(라디오·z 불연속)이 있어 막히면 순회 대신 그룹 규칙을 재게 된다 (실제로 처음에 그렇게 막혔다). 게이트: 테스트 285/285, --edit-smoke 0실패(신규 9), --dialog-shots 넘침 0(대조군 4/4), --scale-budget 5/5, --maxrect 0실패, --cleartype 11/11, --modal-check 0실패, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1b49f07dd9 |
미리보기가 거짓말을 멈춘다, 그리고 9번째 컨트롤이 1번째를 덮지 않는다
3단계 시작 — 실사용 버그 두 건. <b>① 미리보기·인쇄가 인쇄 필터를 무시하고 있었다.</b> 레거시 런타임은 인쇄에서 이렇게 걸러낸다 — If Me.Visible = False OrElse mbPrintOutPut = False Then Return False 그 문장이 ControlRegistry.cs:17 에 인용까지 되어 있는데, 렌더 경로가 두 키를 아무도 읽지 않았다. 대신 걸러낸 것은 ControlElement.Hidden 뿐이고 그건 [JsonIgnore] 디자이너 전용 플래그다 (주석에도 "레거시 Visible 과 별개"라고 적혀 있다). 그래서 인스펙터에서 '인쇄 출력'을 꺼도 미리보기에 그대로 나왔다 — 서식을 저장하기 전에 결과를 확인할 유일한 수단이 거짓말을 하고 있었다. <b>키 이름이 타입마다 다르다.</b> 라벨만 소문자 visible 이다 (Label.vb 가 Shadows Property visible 로 Control.Visible 을 가리고 직렬화기가 그림자를 쓴다). 운영에서 소문자 58,864건(99.7%) 대 대문자 196건이다. 그래서 관용으로 둘 다 읽지 않는다 — 타입이 선언한 키를 카탈로그에서 찾아 그것만 읽는다. 둘 다 읽으면 대문자 Visible=False 인 라벨이 우리 인쇄에서만 빠지고, 그건 호환이 아니라 새 차이다. 모르는 값과 없는 키는 인쇄한다 — 내용을 조용히 빼는 것이 조용히 넣는 것보다 나쁘다. 캔버스는 그대로 다 보여 준다. 편집 중인 것을 못 보면 고칠 수 없다. --db-render 는 이 경로를 쓰지 않아 종이 렌더 md5 는 불변이다(확인했다). <b>② 팔레트 즉시 배치가 9번째부터 겹쳤다.</b> (page.Controls.Count % 8) * 16 이라 두 가지가 틀렸다 — 9번째가 1번째와 정확히 같은 자리에 떨어지고, 하나 지우고 다시 놓으면 지운 자리로 갔다. 단조 증가 카운터로 바꿨다. 계단은 종이를 벗어나기 전에 옆으로 비킨다. <b>순수 함수 테스트만으로는 배선을 증명하지 못한다.</b> PrintFilter 단위 테스트 8건은 판정이 옳다는 것만 말한다. 렌더 경로가 그 판정을 부르는지는 다른 문제이고, 이 앱에서 "코드는 맞아 보이는데 화면은 다른" 일이 이번 세션에만 세 번 있었다. 그래서 --edit-smoke 에서 BuildPageVisual 의 자식 수를 실제로 세는 검사 3건을 더했다. 게이트: 테스트 285/285(신규 8), --edit-smoke 0실패(신규 5), --dialog-shots 넘침 0(대조군 4/4), --scale-budget 5/5, --maxrect 0실패, --cleartype 11/11, --modal-check 0실패, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4150ebe1d7 |
메인 화면을 게이트에 넣고, 최소 크기를 단일 출처로, 창 크기를 기억한다
재설계 1단계 계속. <b>메인 셸이 게이트에 아예 없었다.</b> 사용자가 하루 종일 보는 화면만 자동 검증 밖이었다. 넣고 나서 글자 31개만 검사되는 것을 보고 빈 껍데기를 찍고 있다는 것을 알았다 — 컨트롤을 놓고 하나를 골라 인스펙터·레이어를 채우니 81~95개가 된다. 세 크기·탭으로 찍는다: 기본 1440x920, 현장 최대화 1920x1032, 도구 상자 탭. Loaded 커맨드(서식 목록 DB 조회)는 진단 모드에서 돌지 않게 막았다 — 게이트 결과가 접속 상태에 따라 달라지면 안 된다. <b>게이트가 게이트의 오류를 잡았다.</b> 메인 화면을 넣자 세로잘림 30건이 떴는데 전부 오탐이었다. DesiredSize 는 Margin 을 포함하고 RenderSize 는 포함하지 않아서, 세로 Margin 이 있는 TextBlock 이 예외 없이 걸린 것이다(도구 상자 그룹 머리가 "필요 27 > 실제 14" 였고 그 13 이 Margin). Margin 을 빼고 비교하도록 고쳤다. 이 검사기에서 측정이 나를 정정한 세 번째다. <b>--maxrect 가 최소 크기를 베껴 두고 있었다.</b> ProbeMinWidth=940 / ProbeMinHeight=640 / ProbeBorder=6 은 MainView.xaml 의 복사본이라, 최소 크기를 바꾸면 이 검사가 <b>옛 값을 계속 검사하며 계속 통과한다</b>. 실제 MainView 를 만들어 XAML 이 정한 값을 읽는다. <b>창 크기·위치·상태·두 패널 폭을 기억한다.</b> UserPrefs 가 Dictionary<string,bool> 이라 숫자를 못 담던 것이 원인이었다. JsonElement 로 바꿔 이미 깔린 prefs.json 이 살아남게 했다 — string 이나 double 로 바꾸면 역직렬화가 터져 기존 취향이 통째로 초기화된다. 복원값은 반드시 클램프한다. 이 앱은 WindowStyle=None 이라 제목 표시줄을 직접 그리므로, 저장된 위치가 지금 화면에 없으면(회사에서 두 번째 모니터, 집에서 노트북) 메뉴·저장·닫기가 전부 손에 닿지 않고 마우스로 되돌릴 방법이 없다. 기본 크기로는 이 문제가 안 나지만, 저장을 시작하는 순간 복원되는 것은 사용자가 만든 값이다. 닫기를 물렀을 때는 저장하지 않는다 — 조작하지 않은 값이 남는다. 게이트: 테스트 277/277, --dialog-shots 대조군 4/4 · 글자 2,465개 검사 · 넘침 0건, --maxrect 0실패(MainView 실측 940x640 테두리 6), --cleartype 11/11, --edit-smoke 0실패, --modal-check 0실패, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b0d2e9b470 |
가림막이 대화상자 위에 있었다 — z순서를 명시적으로 내린다
두 번째 오진이다. 지난번에는 가림막이 아예 안 깔렸고, 고친 뒤에는 <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> |
||
|
|
6ae05b73cd |
가림막이 실제로는 안 깔리고 있었다 — 판정 시점을 고친다
직전 커밋(
|
||
|
|
d9c1eaa293 |
모달 뒤를 어둡게 — 대화상자가 떠 있다는 것이 보이게
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> |
||
|
|
893d81a40e |
패널이 선택 단위 — 자식을 집어도 묶음째 움직인다
패널로 감싸 놓고 정작 자식을 클릭하면 자식만 잡혀서 그 하나만 끌려갔다. 그룹처럼 안 움직인다. 이제 컨트롤을 클릭하면 최상위 컨테이너까지 거슬러 올라가 그것을 고른다. 패널이 잡히므로 끌면 자식들이 부모를 따라 함께 움직이고, 자식의 상대좌표는 그대로 남는다. 마퀴로 자식 하나가 걸려도 같은 규칙이라 선택 단위가 한 가지로 유지된다. 안으로 들어가려면 더블클릭한다. 한 번 들어가면 그 자식이 선택된 상태이므로 다시 더블클릭하면 예전처럼 글자 편집이다. 최상위 컨트롤은 들어갈 곳이 없으니 첫 더블클릭이 바로 글자 편집으로 간다(기존 동작 그대로). 이 규칙은 그룹용 패널뿐 아니라 사용자가 직접 만든 패널에도 똑같이 걸린다. 둘을 구분할 방법이 없기도 하고, 구분하면 같은 모양이 다르게 동작해 더 헷갈린다. 디자인 도구의 관례(프레임을 클릭하면 프레임, 들어가려면 더블클릭)와도 맞는다. --edit-smoke 에 3건 추가: 자식을 집으면 패널이 잡히는가, 끌면 패널째 움직이고 자식 상대좌표가 그대로인가, 더블클릭이 안으로 들어가는가. 게이트: 테스트 267/267, --edit-smoke 0실패, --query-popup 17/17, --maxrect 10/10, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ba838ae54a |
피그마식 스냅 — 여러 선이 동시에, 관련된 것만 잇고, 같은 간격이면 그렇다고 말한다
정렬 스냅 자체는 있었다. 다만 축마다 선 하나만, 그것도 화면 끝에서 끝까지 긋는 무한 점선이라 무엇에 맞춘 것인지가 안 보였다. 판단을 SheetMe.Core 로 옮기고 후보의 출처를 끝까지 들고 간다. 왜 옮겼나. 스냅은 눈으로만 확인되는 종류의 코드다 — 후보 하나, 열거 순서 하나가 바뀌면 붙는 자리가 조용히 달라지고 드래그해 보기 전엔 아무도 모른다. Core/Layout 의 SnapSolver 는 순수 기하이고 단위 테스트 27건이 승자 규칙·가이드 개수·선분 범위·간격 계산을 못 박는다. SnapEngine 에는 "어떤 사각형이 후보인가"와 좌표 변환만 남았다. 달라진 것들: · 동시에 맞는 정렬은 전부 보인다. 폭이 같은 형제 옆에 대면 좌·중·우 세 줄이 함께 뜬다. 보정량은 승자 하나만 쓰되, 그 보정을 적용했을 때 실제로 맞는 후보만 선으로 낸다 — |거리|만 같고 부호가 반대인 후보까지 그리면 '붙었다고 그려 놓고 안 붙은' 선이 된다. · 선이 선분이 됐다. 형제 가이드는 관련된 것들의 합집합 범위까지만, 용지 가이드는 그 용지 전체. 같은 좌표의 형제 여럿은 한 선으로 합치고 범위만 넓힌다. 덤으로 대비 문제도 풀렸다 — 예전 분홍(#FF4FA3)은 종이 밖 회색 배경 위에서 2.1:1 로 죽었다. · 허용 오차가 화면 기준이 됐다. 월드 고정이면 줌 50%에서 두 배로 끈적이고 200%에서 절반으로 미끄러진다 — 손끝 감각은 화면 거리로 정해진다. 핸들 히트 반경도 같은 병이라 함께 고쳤다 (줌 300%에서 그림은 8px 인데 판정이 18px 였다). 선 두께도 화면 1px 로 고정한다. · 승자가 결정적이 됐다. 예전에는 컨트롤 열거 순서가 동률을 갈랐다. 이제 |거리| → 종류(형제 모서리 > 형제 중심 > 용지 경계 > 용지 중앙) → 좌표 → 모서리 순이다. · 간격 균등. 형제들이 이미 같은 간격으로 늘어서 있으면 그 리듬에 끼거나 이어 붙는 자리에 붙고, 같은 값이 된 빈틈 전부에 막대와 수치 칩을 놓는다. 양옆만 재면 "30"이 한 번 뜰 뿐이라 무엇과 같아졌는지 안 보인다. 같은 축에서 정렬과 동시에 사정권이면 정렬이 이긴다 — 거리로 겨루게 하면 한 픽셀 차이로 표시가 선↔막대로 튀어 종잡을 수 없다. · 후보 범위. 컨테이너 자식도 이제 후보다(히트테스트는 재귀하는데 스냅만 최상위를 보던 규칙 불일치). 대신 끌려가는 컨테이너의 자식은 빼야 한다 — 선택 목록엔 없지만 함께 움직이므로 빼지 않으면 컨테이너가 제 자식에게 붙어 그 자리에서 굳는다. · 리사이즈. 후보를 이동 드래그에서만 모으고 있어서, 앱을 켜자마자 핸들을 잡으면 스냅이 없고 이동을 한 번 한 뒤에는 그때의 낡은 좌표에 붙었다. 리사이즈 시작에도 모으고 놓을 때 버린다. 모서리 스냅이 '첫 매치'였던 것도 이동과 같은 최근접 규칙으로 통일했다. 가이드도 함께 보인다. · 정합된 축은 반올림하지 않는다. 형제 폭이 홀수면 중심이 반정수라, 반올림하면 중앙에 맞췄는데 0.5px 어긋난 채로 놓인다. 무한선일 땐 안 보였지만 선분이 되면 모서리에서 뜬 게 눈에 띈다. 저장은 LegacyFormat.FormatPair 가 어차피 정수로 쓰므로 포맷은 영향 없다. 성능 가드: 한 줄에 겹치는 형제가 64개 넘으면 간격 계산을 건너뛰고(정렬은 정상), 형제가 2000개 넘으면 간격을 끈다. 후보 정렬은 드래그 시작에 1회, 프레임 경로는 무할당. 가이드 내용이 이전과 같으면 컬렉션을 건드리지 않는다. 신규 진단 --snap-shots — 종이와 오버레이를 겹쳐 드래그 도중을 찍는다. 기존 --render-smoke/--db-render 는 종이만 찍어 오버레이가 안 나왔다. 선이 어디서 어디까지 그어지는지, 몇 개가 동시에 보이는지는 그림으로만 확인된다. 게이트는 함께 남기는 _report.txt 의 수치로 걸고 PNG 는 눈 검증 보조다(픽셀 비교는 테마·글꼴에 흔들려 게이트로 못 쓴다). 게이트: 테스트 257/257(신규 27), --edit-smoke 223건 0실패(스냅 검사 14건 신규), --query-popup 17/17, --maxrect 10/10, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일, --snap-shots 7종 0실패. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8a7cc65099 |
최대화가 작업표시줄을 덮던 것 — 창을 작업영역에 맞춘다
메인 창은 WindowStyle=None + WindowChrome 으로 크롬을 직접 그린다. 그 조합에서 OS 는 최대화 사각형을 작업영역이 아니라 모니터 전체를 기준으로 잡고 리사이즈 프레임만큼 더 부풀린다. 실측으로 창이 (-7,-7)~(1927,1087) — 작업영역 (0,0)~(1920,1032) 를 55px 넘겨 작업표시줄이 통째로 가려졌다. 표준 크롬 창에는 없는 문제라 WindowStyle=None 창에만 건다. WM_GETMINMAXINFO 훅 하나로 답한다(Services/MaximizeToWorkArea.cs). 레이아웃·XAML·테마는 건드리지 않았다. 최대화 상태에서 콘텐츠가 밀려 보인다면 답은 Margin 보정이 아니라 이 훅의 계산이 틀린 것이다 — 창 사각형이 정확히 작업영역이면 잘릴 곳이 없다. 같이 처리한 것들: · 최소 크기가 최대화를 이기는 경우. OS 는 최대화 크기를 최소 트래킹 크기까지 되밀어 올린다. 그 값은 우리 뒤에 도는 WPF 가 MinWidth/MinHeight(940x640 DIP)로 채우므로, 작업영역이 그보다 작으면 창이 도로 부풀어 작업표시줄을 다시 덮는다 — 1366x768 을 125% 로 쓰면 최소 높이 800px 에 작업영역 720px 다. 그 경우에만 WPF 의 처리를 막고 트래킹 크기를 직접 채운다. 화면에 안 들어가는 최소 크기는 최소 크기 구실을 못 한다. 보통은 handled=false 로 두어 MinWidth/MinHeight 가 그대로 산다. · 최대화 중 상단 6px 리사이즈 띠. 전에는 창이 화면 밖으로 밀려 있어 그 띠가 안 보였다. 이제 타이틀바 위로 올라와 닫기 버튼 윗변이 먹혔다(실측 y=1·3·5 에서 HTTOP). 최대화된 창은 어차피 가장자리로 크기를 못 바꾸므로 그동안 띠를 걷는다. 여백 보정이 아니라 히트테스트 영역 보정이다 — 창 사각형은 그대로 작업영역이다. · 자동 숨김 작업표시줄. 자동 숨김이면 작업영역이 모니터 전체와 같아지고, 그대로 꽉 채우면 셸이 전체 화면 앱으로 보아 가장자리 호버에 반응하지 않는다 — 작업표시줄이 영영 안 나온다. 네 변을 각각 ABM_GETAUTOHIDEBAREX 로 보고 1px 씩 물러선다. 폴백은 '한 변도 못 찾았을 때'가 아니라 '그 변을 못 찾았을 때' 돈다(좌측 서드파티 바 하나 때문에 하단 작업표시줄이 구제받지 못하던 구조였다). 폴백이 쓰는 두 API 는 주 작업표시줄 전용이라 보조 모니터는 못 덮는다 — 주석을 그렇게 바로잡았다. · 재부착 가드. 최대화 중에 두 번 붙으면 그때의 0 을 '복원 시 두께'로 기억해 복원해도 가장자리를 끌 수 없는 창이 됐다. 진단 종료 코드가 죽어 있었다. 기본 ShutdownMode 는 OnLastWindowClose 라, 창을 띄웠다 전부 닫는 진단은 마지막 창이 닫히는 순간 WPF 가 먼저 Shutdown(0) 을 걸고 뒤이은 Shutdown(코드) 인자를 무시한다 — 리포트에 '실패 1건' 이 찍혀도 프로세스는 0 을 돌려줬다(--maxrect 첫 실행이 그랬다). --edit-smoke·--db-smoke 도 같은 구멍이었다. 진단 분기 진입부에서 OnExplicitShutdown 으로 바꿨다. 같은 자리에서 진단 모드 UI 예외도 모달 대신 로그+Shutdown(3) 으로 돌린다 — '진단 모드에서는 모달을 띄우지 않는다'는 규칙을 그보다 먼저 등록되는 예외 핸들러가 깨고 있었다. 신규 진단 --maxrect (10건). 기대값을 베껴 적지 않고 성질을 단언한다 — 모니터 안에 들어가는가, 자동 숨김 없는 변은 작업영역과 같은가, 있는 변은 물러섰는가, 작업표시줄과 겹치지 않는가, 최소 크기가 최대화 크기를 넘지 않는가. 전사(轉寫)하면 같은 착각을 양쪽이 공유하고 양보 폭을 1→2 로 바꾸는 순간 거짓 실패가 난다. 보내는 버퍼는 센티넬로 채운다 — 0 으로 두면 기대값이 (0,0) 인 모니터에서 훅이 아무것도 안 써도 통과한다(실제로 한 번 그렇게 통과했다). 창을 실제로 띄워 최대화·복원까지 해 OS 가 답을 지키는지와 StateChanged 배선까지 본다. 그 경로에서 화면이 한 번 번쩍인다. 실측(1920x1080·96DPI·하단 고정 작업표시줄): 최대화 (-7,-7)~(1927,1087) → (0,0)~(1920,1032), 침범 55px → 0px 복원 1440x920 유지, 최소 크기 940x640 유지 최대화 상단 히트테스트 HTTOP → HTCAPTION 자동 숨김·다중 모니터·125%/150% 배율은 이 개발기에서 재현할 수 없다. 그 환경에서 --maxrect 를 한 번 돌려 봐야 한다. 게이트: 테스트 230/230, --edit-smoke 0건, --query-popup 17/17, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일, --dialog-shots 51개, --maxrect 10/10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3e26af8739 |
쿼리 편집기 — 한글에서 색과 글자가 어긋나던 문제, 다크 제목 표시줄, 목업 맞춤
■ 한글을 치면 색칠이 글자와 어긋났다 (가장 중요) 색칠 레이어가 글자 위치를 <b>직접 재서</b> 잡고 있었다. 앞부분을 FormattedText 로 측정해 그 폭만큼 오른쪽으로 옮겨 그리는 방식이다. ASCII 에서는 맞았지만 한글에서 무너졌다 — 편집 글꼴(Consolas)에 한글 글리프가 없어 폰트 대체가 일어나는데, TextBox 의 내부 조판과 이쪽 측정이 같은 글꼴·같은 셰이핑을 고르리라는 보장이 없다. 조금만 달라도 뒤로 갈수록 벌어져 색과 글자가 따로 논다. 이제 <b>TextBox 에게 좌표를 묻는다</b>(GetRectFromCharacterIndex). 여백·스크롤·줄 높이·폰트 대체가 전부 TextBox 기준으로 이미 반영돼 오고, 조각마다 위치를 새로 물으므로 오차가 누적되지 않는다. 줄 높이를 손으로 계산하던 코드도 함께 사라졌다. 편집 글꼴 순서도 한글이 있는 것(D2Coding → 굴림체 → Consolas)으로 바꿔 대체 자체를 줄였다. --query-popup 진단에 정렬 검사 2건을 넣었다: 한글이 섞인 줄에서 글자 좌표가 뒤로 밀리지 않는가, 마지막 글자 다음 좌표가 캐럿 자리와 이어지는가. 현재 9/9. ■ 다크 모드인데 제목 표시줄만 밝게 남던 문제 ThemedWindow 스타일은 창 <b>안쪽</b>만 칠한다. 제목 표시줄은 OS 가 그리고 기본값은 Windows 의 테마를 따르므로, 앱을 다크로 바꿔도 Windows 가 라이트면 흰 띠가 남는다. DWM 속성(DWMWA_USE_IMMERSIVE_DARK_MODE)으로 창별로 지정한다. 커스텀 크롬을 직접 그리는 것보다 훨씬 싸고, 최소화·최대화·닫기의 동작과 접근성이 OS 것 그대로 남는다. 창마다 손으로 넣으면 새 대화상자에서 반드시 빠지므로 App 에서 Window 클래스 핸들러로 한 번만 걸었다 — 지금 있는 창과 앞으로 만들 창 모두 자동으로 적용된다. 테마를 바꾸는 순간 이미 떠 있는 창들도 함께 맞춘다(ThemeManager.ApplyChromeToOpenWindows). ■ 목업과 위치 맞춤 앞 배지(아이콘) · 검색칸의 돋보기와 안내 문구 · 안내 상자의 ⓘ 를 넣었다. 갈래 칩 순서는 개수순에서 <b>고정 순서</b>(환자·서식·작업자·컨트롤)로 바꿨다 — 컨트롤 수는 서식마다 달라서 개수순으로 두면 서식을 바꿀 때마다 칩이 자리를 바꿔 손이 기억하지 못한다. 회귀: 테스트 230/230, 편집 스모크 실패 0, 팝업·정렬 점검 9/9, 검증 실행 점검 10/10, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
59b0e457fe |
2단계 — 신규 서식에 런타임 설정을 걸 수 있게 (선 3종·공통 속성·키 자동완성)
레거시로는 되는데 SheetMe 로는 못 만들던 것들을 메웠다. 어떤 속성을 올릴지는 취향으로 정하지 않고
--db-props 로 운영 디자인 1,271건 / 컨트롤 164,091개를 세고, 각 컨트롤 .vb 의 필드 선언을 읽어 정했다.
■ 선(Line) — 새로 그은 세로선이 EMR 에서 사라지던 문제
MLine 은 방향을 크기가 아니라 Orientation 으로 정한다(MLine.vb:181-190).
키가 없으면 Horizontal 이라 (0,0)→(Width,0) 을 그리므로, 폭 1·높이 210 으로 세로처럼 만든 선은
디자이너에만 보이고 기록지에서는 1px 점이 된다. 운영 선 32,599개 중 Orientation 보유 12,811개이고
그 중 Vertical 이 12,721개 — 세로선은 예외가 아니라 주류다.
Orientation·BorderWidth·DashStyle 을 인스펙터에 올렸다. 그리고 값만 올리지 않고
레거시 컨트롤이 스스로 지키는 불변식을 함께 옮겼다(LineGeometry):
· 두께 축은 항상 BorderWidth 다 — MLine.OnResize 가 매번 되돌린다. 그래서 선을 끌어
두껍게 만들 수 없다. 이걸 허용하면 '세로처럼 보이지만 EMR 에선 점'인 선을 다시 만들 수 있다.
· 방향 전환은 길이를 보존한 채 축을 바꾼다(레거시 Orientation 세터: Width = Height 후 OnResize).
· 가로로 되돌리면 Orientation 키를 지운다 — <DefaultValue(Horizontal)> 라 레거시 직렬화기도 생략한다.
--db-lines 를 새로 만들어 확인한 결과 운영 선 32,599개에서 경계 모양과 Orientation 불일치는 0건이다.
레거시가 구조적으로 막아 왔다는 뜻이고, 그래서 캔버스 렌더는 건드리지 않았다(P062 바이트 동일).
■ 공통 런타임 속성 — 서명란·인쇄 제외·필수입력·자동높이·재조회
신규 컨트롤은 Props 가 사실상 비어 있어 '전체 속성(0개)'이었고, 고급 편집도 이미 있는 키만 나열한다.
결과적으로 서명이 필요한 동의서나 점수를 합산하는 평가지를 SheetMe 만으로 새로 만들 수 없었다.
타입별 기술자에 다음을 추가했다(기본값은 각 컨트롤 .vb 필드 선언에서 확인한 값이다):
PrintOutPut(기본 True) · PreventEditing · IsRequiredValue(No/Yes) · Visible · AutoHeight ·
ReLoadData 3종 · MPictureBox 의 IsSignature/SignatureIndex.
PrintOutPut 을 True 로 표시하는 것이 중요하다 — 키가 없는 컨트롤이 '인쇄 안 함'으로 보이면
사용자가 껐다 켜는 순간 명시 False 가 기록돼 실제로 인쇄에서 빠진다
(런타임 필터: If Me.Visible = False OrElse mbPrintOutPut = False Then Return False).
■ 감사 권고와 다르게 한 것 3가지 — 근거가 반대였다
1. Score 를 5개 타입에 추가하라는 권고는 따르지 않았다. 실제로 저장값을 읽는 것은
CheckBox·RadioButton 뿐이다(게터가 mdScore 반환). TextBox·MaskedTextBox 의 Score 게터는
저장값을 무시하고 Me.Text 를 숫자로 읽으며, CalcBox 는 세터가 오히려 Text 를 덮어쓴다.
편집기를 붙였으면 사용자는 배점을 걸었다고 믿고 런타임은 무시하는 상태가 된다.
ComboBox 는 Score 가 아니라 ItemScore("/" 구분, 항목 순서와 1:1)를 노출했다.
실측이 먼저 신호를 줬다: ComboBox 의 Score 값 분포가 Text 와 정확히 같았다(-×625, ++×22).
2. Button 에는 PrintOutPut 을 붙이지 않았다 — MButton.vb 에 그 속성 자체가 없다.
3. 라벨의 표시 여부는 소문자 visible 이다. Label.vb:209-217 이 <Browsable(False)> Shadows 로
Control.Visible 을 가리고 직렬화기가 그 그림자를 쓴다. 운영에서도 소문자 58,864건(99.7%)
대 대문자 196건. 대문자로 쓰면 라벨은 그대로 보인다.
■ '속성 추가' 자동완성
큐레이션 밖의 값을 걸려면 키를 손으로 쳐야 했는데, 철자가 틀려도 경고가 없고
그 서식은 배포된 뒤에야 이상하게 동작한다. 타입별 실사용 키 목록(LegacyPropertyCatalog,
--db-props 집계 기반)을 제안 칩으로 깔았다. 이미 있는 키와 인스펙터가 이미 다루는 키는 빼고,
부분 일치로 좁히며 접두 일치를 앞에 둔다. 자유 입력은 그대로 열어 뒀다 — 제안이지 검증이 아니고
사이트가 추가한 키도 있다.
■ 1단계 잔여분: 선택 밖 컨트롤을 Ctrl+드래그하면 엉뚱한 것이 끌리던 문제
Ctrl/Shift 클릭은 선택을 바꾸지 않고 업에서 토글하므로, 그 상태로 드래그가 시작되면
잡은 컨트롤이 아니라 기존 선택분이 끌려갔다(선택이 비어 있으면 아무것도 안 끌렸다).
드래그가 실제로 시작되는 지점에서 잡은 것을 선택에 넣는다 — 기존 다중선택은 유지한다(업 토글과 같은 규칙).
편집 스모크 14건 추가(선 기하 6 · Ctrl+드래그 2 · 자동완성 5 · 라벨 소문자 함정 1).
회귀: 테스트 124/124, 편집 스모크 183건 실패 0,
DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8eeff42a72 |
전수조사 1단계 — 저장된 서식을 오염시키는 결함 7건 수정
레거시 대비 전수조사에서 나온 출시 차단 9건 중, 하루 안에 끝나는 국소 수정 7건. 저장 데이터를 망가뜨리는 ③④ 를 먼저 고쳤다. ③ 페이지 복제·추가가 루트 이름을 중복 발급했다 Clone 이 Root.Id 까지 베끼는데 자식만 재발급해서 한 서식에 MDesignerHost1 이 둘 생겼다. AddPage 도 '페이지 수 + 1' 로 지어서 루트가 1·3 인 2장짜리 서식에서 곧바로 3 이 겹쳤다. E_SctMst 동기화는 SctObjNam 사전이라 둘째 루트가 첫째 루트 행에 매핑되고, 그 페이지 자식들의 SctParObj 가 없는 ObjID 를 가리킨 채 남는다. → 두 경로 모두 RenamePageRoot 로 이름과 Name 속성을 함께 새로 받는다. ④ 이름 채번이 빈 번호를 메웠다 TextBox3 을 지우고 새로 놓으면 다시 TextBox3 이 나왔다. 이름은 저장된 답변 (E_SctDta.SctObjNam)이 붙는 열쇠라서, 지운 칸의 과거 입력이 새 칸에 그대로 나타난다. 액션 대상과 CalcBox 수식도 이름으로 서로를 가리키므로 조용히 오배선된다. → 레거시 clsNameCreationService 규약대로 '사용 중 최대번호 + 1'. 숫자가 아닌 접미사(TextBoxTotal)는 번호로 보지 않는다. ① 탭순서 적용이 문서 전체를 밀었다 한 페이지를 편집해도 전 페이지 컨트롤이 999999+i 로 초기화됐다. 레거시는 페이지마다 별도 디자인 서피스라 다른 페이지는 건드리지 않는다. → 초기화 범위를 이번에 클릭한 페이지로 한정. ② 이력본이 열린 상태에서 현재본을 열면 이력 탭이 활성화됐다 FindOpenDbDocument 가 ShtCod 만 보고 있었다. 사용자는 현재본을 고친다고 믿은 채 읽기 전용 스냅샷을 편집하게 된다. → HistorySdgKey is null 조건 추가. ⑤ 레거시에 없는 태그 3종을 고를 수 있었다 PAT_BMI · PAT_IBW · PAT_IBW_퍼센트 는 bzDataInterface.vb:922/957/970 에서 주석 처리된 채로 남아 있어 런타임에 해석되지 않는다. 고르면 배포 후에야 빈칸으로 드러난다. → 카탈로그에서 제거(값은 자유 입력이라 기존 서식에는 영향 없다). ⑥ 신규 서식 등록이 ShtHspYon·ShtExpDte 를 비웠다 --db-shtmst 진단을 신설해 실측한 결과 운영 E_ShtMst 1,737행 중 두 컬럼이 NULL 인 행은 0건이다. ShtExpDte 는 dtEMRLoader.vb:1229 가 NVL(ShtExpDte,' ')='29991231' 로 못박아 거르고, 여러 조회가 sysdate BETWEEN ShtAdpDte AND ShtExpDte 를 쓴다 — NULL 이면 BETWEEN 이 거짓이라 등록은 성공했는데 EMR 어디에서도 안 보이는 서식이 된다(29991231 이 1,718행 98.9%). ShtHspYon 은 기록지정보·자동생성 목록이 'Y' 로 거르며, 디자이너가 만든 서식 169건 중 160건이 'Y'. → 두 값을 INSERT 에 넣는다. ⑦ 빈 선택 상태의 Ctrl+드래그가 예외로 죽었다 Ctrl/Shift 클릭은 선택을 바꾸지 않고 업에서 토글하므로 주선택이 빈 채 드래그가 시작될 수 있는데, BeginMove 가 Selection.Primary! 를 그대로 넘겨 RootAncestorOf 가 즉시 역참조했다. → 앵커를 Primary ?? 첫 선택으로 완화. 편집 스모크 9건 추가(루트 이름 유일 2 · 채번 4 · 탭순서 페이지 격리 1 · 빈 선택 드래그 1 · 태그 3종 부재 1). 채번 검사는 '가운데 번호'를 지운다 — 최대 번호를 지우면 최대값 자체가 내려가므로 같은 이름이 다시 나오는 것이 레거시와 같은 정상 동작이다. 회귀: 테스트 124/124, 편집 스모크 169건 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일(HEAD 대비 md5 일치). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
604f8f7d1b |
순정 MessageBox 를 앱 테마 대화상자로 교체 (53곳 중 49곳)
다크 테마에서 흰 시스템 창이 튀어나오던 알림을 자체 창(MessageDialogView)으로 바꿨다. 앱 셸과 같은 커스텀 타이틀바(36px) + B.Surface 본문 + 성격 아이콘 + 우측 버튼 줄. 폭 440 고정, 내용에 따라 높이 자동, 긴 예외는 스크롤로 흡수한다. 진입점은 DialogService 정적 메서드 4개로 모았다 — Notify / Confirm / ConfirmWithCancel / ShowError. MessageBox 는 Win32 호출이라 아무 데서나 되지만 WPF 창은 아니다. 교체로 새로 생기는 제약 셋을 DialogService 한곳에서 막는다. - 진단 모드에서는 창을 만들지 않고 기각 기본값을 즉시 돌려준다. 스모크는 무인 실행이라 모달이 하나라도 뜨면 타임아웃 없이 영원히 멈춘다 — 실제로 편집 스모크가 지나는 경로에 붙여넣기·개명 가드가 있다. 규약을 스모크 검사 3건으로 고정했다. - UI 스레드가 아니면 Dispatcher 로 넘긴다. - 소유 창은 '살아 있는 것'만 건다. 아직 안 보였거나 이미 닫힌 창을 Owner 로 주면 예외이고, 진단 렌더러가 도는 동안 MainWindow 가 닫힌 창을 가리킬 수 있다. App.xaml.cs 4곳은 다르게 처리했다. - 기동 실패(:49)·예외 폭주(:82)·복구 안내(:89) 3곳은 순정 유지. 창이 없는 시점이라 자체 창을 띄우면 종료코드가 유실되고, 이미 예외가 터진 자리에서 WPF 창을 새로 만들면 같은 핸들러로 재진입한다. 이유를 각 자리에 주석으로 남겼다. 덤으로 :89 는 e.Handled 를 알림보다 먼저 세우도록 순서를 바로잡았다 — 알림이 던지면 '복구 가능한 예외'가 하드 크래시로 바뀐다. - 알 수 없는 진단 옵션(:179)은 알림을 없앴다. 옵션 오타 하나로 무인 실행이 멈추던 자리다. 함께 고친 것 — Primary 버튼 스타일. 공유 버튼 템플릿의 호버 트리거가 TargetName 으로 채움을 회색으로 덮는데(TargetName 트리거는 TemplateBinding 을 이긴다) 글자는 흰색 그대로라, 라이트에서 마우스를 올리면 #F3F3F3 위 흰 글자 1.08:1 로 사라진다. 지금까지 Primary 사용처가 0건이라 드러난 적이 없었고 이 창이 첫 사용이다. 전용 템플릿 + 채움 호버·누름 토큰 2종 신설. 신설 토큰: B.Warning / B.Danger(라이트는 다크값을 못 쓴다 — 앰버 #E0A33A 는 흰 면 위 2.22:1 로 아이콘 기준 3:1 도 미달), B.AccentFillHover / B.AccentFillPressed. 전부 양 테마에 동시 추가. Lucide 아이콘 4종(info·circle-alert·triangle-alert·circle-help) 추가. 바꾸지 않은 것: 예외 원문. 18곳을 ShowError 로 수렴시키면 화면에서 ex.Message 가 오류코드+로그로 대체되는 동작 변경이 된다 — 요청은 시각 변경이라 문구·정보량을 그대로 뒀다. 남는 시스템 대화상자: 인쇄(PrintDialog)와 파일 열기/저장 — OS 셸 대화상자라 대상이 아니다. 진단 렌더러에 알림 5종(오류·경고·확인·저장확인·정보)을 등록해 라이트/다크 10장이 자동으로 남는다. 회귀: 테스트 124/124, 편집 스모크 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ca16630f3a |
서식 목록을 분류(ShtClsCod)로 묶기 — 평면 1,629건 → 접이식 24분류
사용자 지적: "서식 목록 구분없이 하나로 다 나오는거 불편한데 묶여서 나와야해". [기준 선정] 후보 컬럼 7종을 실DB로 전수 집계해(신규 --db-cls) ShtClsCod 를 택했다. 이것만이 판정 3기준을 통과한다 — 그룹 24종, 최대 그룹 269건(16%), 빈값 1건(0.06%). 나머지는 전부 한쪽 쏠림이다: ShtTyp D 79% / ShtPatTyp C 92% / ShtLstTyp NONREC 88% / ShtDepCls 빈값 74%. 의미상으로도 ShtClsCod 만 '분류'이고 나머지는 동작 규칙이다(ShtWrtTyp=1장 제약, ShtUsrTyp· ShtPatTyp=작성 게이트, ShtOprTyp=.NET 클래스명, ShtDspSeq=정렬키). 무엇보다 이건 사용자가 이미 아는 축이다. 레거시에서 서식생성기를 여는 화면(기록지정보 fmShtMst) 상단이 `[ 분류별 전체 ]` + 분류별 탭이고, EMR 기록지 트리 1단 헤더도 같은 값이다. 재교육 비용 0. [라벨] 코드→한글명은 M_DtlMst 의 DtlTblCod='EMR_SHEET_GROUP' 이 소유한다. 소스 어디에도 매핑표가 없고 병원별 마스터라(HspCod 분기가 레거시에 다수) 하드코딩하면 다른 병원에서 즉시 오답이 된다. 런타임 조인만 쓴다. 실측: 마스터 24행 ↔ 서식 24코드 완전 일치, 고아 코드 0건. [조인 방향] E_ShtMst 드라이빙 + LEFT JOIN 이다. 레거시 EMR 트리(bizSheetList.vb)는 M_DtlMst 를 드라이빙으로 써서 분류 미지정 서식이 목록에서 통째로 사라지는데, 열기 목록에서 그건 "그 서식을 영영 못 여는" 장애다. 기록지정보 화면 방식(dtShtMst.vb:75-81)을 따랐다. [조인 증식 방어] M_DtlMst 는 PK 에 DtlStrDte 가 들어가 같은 코드가 기간별 다중행일 수 있다. 그대로 조인하면 서식이 목록에 두 번 뜬다. 실측상 현재 증식 0건이지만 그건 데이터 운이지 쿼리가 막은 게 아니므로, ROW_NUMBER 로 적용일 최신 1행만 택하게 했다. [그룹 순서] LPAD(DtlDspSeq,4,'0') 우선, 그다음 코드순, 미분류는 맨 뒤. 병원이 정한 표시순서가 1순위여야 레거시 분류 탭과 같은 자리에 뜬다. 이 병원은 DspSeq 가 전부 비어 있어 결과적으로 코드순(A~X)이지만, 채워 쓰는 병원에서 어긋나지 않는다. LPAD 는 레거시의 2자리 대신 4자리 — '100' 과 '99' 가 뒤집히는 것을 막는다. [상한 300 → 2,000] 종전 300 상한은 정렬이 분류순으로 바뀌면서 성격이 달라진다. 코드순 300 은 고르게 잘렸지만 분류순 300 은 뒤쪽 분류가 통째로 사라져 "그 분류가 없는 것"처럼 보인다. 모집단 1,629건이라 이제 전량이 온다. 도달 시 하단에 잘렸다고 명시한다(조용한 절단 금지). [분류명도 검색 대상] 헤더에서 본 '간호기록'을 그대로 쳤는데 0건이면 배신감이 크다. LIKE 토큰에 DtlCodNam 을 추가했다. [접기 상태] 기본 접힘 + 검색 시 자동 펼침. 다만 레거시는 서식이 꽉 찬 평면 목록으로 시작하므로 접힌 첫 화면을 낯설어하는 사용자가 있다 — 전체 여닫기 토글을 검색칩 옆에 두고, 그 선택을 %LocalAppData%\SheetMe\prefs.json 에 보존한다(신규 UserPrefs). 매 실행마다 다시 펴지 않아도 된다. [WPF 함정] 그룹핑을 걸면 WPF 는 기본적으로 가상화를 끈다 — 1,600여 항목이 한꺼번에 실체화돼 탭 전환이 눈에 띄게 멈춘다. IsVirtualizingWhenGrouping + VirtualizationMode=Recycling 필수. 그룹 키를 문자열이 아니라 SheetGroupViewModel 인스턴스로 둔 것도 의도다 — CollectionViewSource 가 값 동등성으로 묶으므로 코드당 같은 인스턴스를 주면(SheetGroupRegistry) 헤더에서 Name.IsExpanded 를 TwoWay 로 바인딩해 접기 상태를 VM 이 소유할 수 있다. [열기 대화상자] 같은 ListSheets 를 쓰는 SheetOpenDialogView 는 그리드라 트리 대신 '분류' 열을 추가했다(같은 정보, 그 화면에 맞는 형태). [진단 모드 신설] --db-cls (분류 후보 분포 + 라벨 마스터 + 조인 증식 검사 + 고아 코드 + 모집단), --db-tables <패턴> (이름 모르는 마스터 테이블 탐색). 둘 다 읽기 전용. 검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0, **종이 렌더 P062 픽셀 대조 차이 0**. 실 UI: 24분류 한 화면 · 의사기록 펼침 266건 · '경과기록' 45건/6분류 자동 펼침 · '간호기록' 204건/4분류(분류명 매칭 동작 확인). 별건 보고: 목록 모집단에 폐기 서식(ShtUseYon='N')이 452건(28%) 섞여 그룹 건수를 부풀린다. 레거시 서식생성기도 이 필터를 걸지 않으므로 이번엔 동작을 바꾸지 않았다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
71862c2986 |
배포 준비 — 자격증명 분리, 산출물 축소, 로깅·전역 예외
[자격증명] 접속 문자열 우선순위를 환경변수 > appsettings.Development.json > appsettings.json > ServerInfo ini 로 세웠다. 병원 설치본은 설정 없이 [000]bin 의 MSYSTECH_ServerInfo.ini 를 자동으로 찾아 붙는다(레거시와 동일한 MD5 → 3DES-ECB 복호). - ServerInfoReader 를 [200]SheetMe 에서 복사 이식(vendoring — 두 저장소가 분리돼 있어 ProjectReference 불가, 레거시 암복호 규약은 변할 이유가 없는 고정 자산). 헤더에 동기화 의무 명시. - 실측 검증: ini 566라인/병원 엔트리 76개, CurrentServer=029.BSGH 복호 후 실제 접속 성공. - SaveMode 기본값을 File 로 되돌렸다. ini 의 CurrentServer 가 운영 병원 DB 를 가리키므로 DB 쓰기는 명시적으로 켠 단말에서만 활성화되어야 한다. - ConfigService 로 단일 소유화 — ConfigLoader.Load() 11회 호출이 ini 파싱 + 3DES 복호를 매번 반복하던 것을 1회로. 죽어 있던 Designer:GridSize/SnapThreshold 를 SnapEngine 에 배선하고, 쓰이지 않던 His:Provider 와 DefaultPaperWidth/Height 는 제거했다(후자는 설정이 아니라 레거시 패리티 상수다). [산출물] 8.6MB/34파일 → 7.2MB/26파일. - EmrDataContext 제거 — M.Framework.DBAccess/TableFramework/M.MW.Data.EMR 의 유일한 소비처였는데 그 클래스가 어디서도 인스턴스화되지 않았다. DB 접근은 전부 raw Oracle 클라이언트를 쓴다. - Microsoft.Web.WebView2 는 ExcludeAssets="runtime" — M.Framework.WPF 전이 의존일 뿐 소스 참조 0건. [000]bin 과의 이름 충돌 3건도 함께 사라진다. - Production.pubxml(win-x64, FDD, SatelliteResourceLanguages=ko). RID 를 csproj 가 아니라 pubxml 에 둔 이유는 csproj 에 넣으면 dotnet build/test 까지 RID 별 복원을 타기 때문이다. - tools/publish.ps1 — 비밀값 하드 게이트 + [000]bin 충돌 경고 + SHA256 매니페스트 + zip. 게이트는 역방향으로 검증했다(appsettings.json 에 실접속 정보를 넣고 실행 → 정상 차단). - nuget.config 신설 — 사내 피드가 개발자 개인 OneDrive 경로라 다른 머신에서 복원이 불가능했다. %MSYS_NUGET_FEED% 환경변수로 받게 해 최소한 실패 원인이 드러나게 했다. [배포 규약] docs/DEPLOYMENT.md. [000]bin 최상위 평면 복사를 금지한다 — 실측 결과 Oracle.ManagedDataAccess.dll 이 겹치고 (신규 .NET Core 5,434KB ↔ 기존 .NET FW 4,602KB), 덮으면 그 폴더의 레거시 EXE 187개가 전부 Oracle 접속 불능이 된다. [000]bin\SheetMe\ 하위 폴더에 둔다 — Information/Log/ OCR서식생성기/SpreadDesign 등 기존 앱들과 같은 방식이다. FDD 로 배포한다: [000]bin\OCR서식생성기 가 이미 net10.0 + WindowsDesktop.App 10.0.0 을 요구하며 운영 중이라 런타임 존재가 확인된다. 없는 단말이 나오면 -SelfContained 한 번이면 된다. [로깅] AppLog — LogManager 배선. 모든 호출을 try/catch 로 감싸 로깅 실패가 업무를 막지 않게 했다. - Redact 필수 — 접속 문자열을 값으로 들고 다니므로 예외 메시지에 자격증명이 섞일 수 있다. 기록 직전 1회 통과시킨다. - LogLevel 은 열거형이 아니라 문자열 속성이라 오타를 컴파일러가 못 잡고, 잘못된 값이면 FIXED 만 남고 나머지가 조용히 사라진다. LogType 열거값의 이름으로만 지정하게 했다. - 문서에 있는 HandleShutdown 은 6.0.0 DLL 에 실제로는 없어(XML 문서가 앞서 있음) 쓰지 않는다. 대신 기록이 비동기 배치라 스모크에서 짧게 폴링해 확인한다. - 로그 경로는 실행 폴더\logs\Designer, 쓰기 불가 시 %LocalAppData% 폴백(쓰기 프로브까지 확인). [전역 예외] Dispatcher/AppDomain/TaskScheduler 3종을 진단 분기보다 앞에 등록했다. UI 예외는 기록 후 계속 진행한다(편집 중 문서를 예외 하나로 잃지 않게) — 단 10초 내 5회면 무한 팝업 루프이므로 강제 종료한다. DialogService.ShowError 도입 — 우리가 던진 안내성 예외는 메시지를 그대로 보여주고, 그 외는 일반화 문구 + 오류 코드만 노출한다(코드가 로그 줄머리와 같아 전화 한 통으로 특정된다). ex.ToString() 전문을 그대로 띄우던 2곳을 정리했다. 로그인/권한거부/DB저장은 감사 이벤트(FIXED)로 남긴다. 검증: 테스트 70/70, edit-smoke 실패 0(마스킹 5건 + 로그 배선 1건 추가), 왕복 1,271건 diff 0/예외 0, db-save-smoke 제자리 갱신 통과. publish.ps1 정방향/역방향 모두 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0f77cb717a |
HIS 사용자 컨텍스트 + 서식생성기 권한 게이트 + 서버 시각 전환
[사용자 컨텍스트] 감사 컬럼에 Windows 계정명이 들어가던 6지점을 HIS UidCod 로 교체했다. 레거시 이력 화면(ucSheetHistory.vb:51)이 SdgUidCod 를 수정자로 표시하므로 지금까지는 감사 추적이 끊겨 있었다. - UserContextStore — M_UidMst 단독 조회로 인증 판정(레거시 bzLogIn 자동로그인과 동일 의미론, 비밀번호를 보지 않고 행 존재 + 유효기간만 확인). 부서/병원은 LEFT JOIN best-effort 표시용. 적용일자는 서버 시각을 쓴다 — 단말 시계가 밀리면 유효 계정이 거부될 수 있다. - UserSession — 정적 set-once. 생성자 주입을 택하지 않은 이유는 진단 모드(--db-*)가 ViewModel 그래프 밖이라 주입으로는 감사 기록 지점에 닿을 수 없기 때문이다. 대신 읽는 곳을 App 초기화 / FormDesignDataBusiness / MainViewModel 표시 세 군데로 제한했고, 저장소는 지금처럼 uid 를 파라미터로만 받는다. 진단 모드는 "SMOKE" 식별자를 쓴다. - StartupArguments — 레거시 규약 "UidCod,ComNum,ShtCod" 관용 파싱(PadRight 후 split) 이식. ComNum 은 레거시에서도 선언·대입뿐인 죽은 인자라 보존만 하고 쓰지 않는다. - App.OnStartup 재구성 — 진단 플래그 12종을 RunDiagnostic 으로 분리(각 분기가 종료 코드를 반환하도록 정리), 세션 확정 후 MainView 표시. 기동 인자에 ShtCod 가 있으면 PendingSheetCode 로 넘겨 빈 새 문서를 만들지 않고 그 서식만 연다(열기 실패 시에는 빈 문서로 폴백). - 인자로 받은 UidCod 조회에 실패하면 기동을 중단한다(레거시 :892 와 동형). 인자 없이 단독 실행하면 미인증으로 계속하되 DB 쓰기를 차단하고, 개발 편의는 His:DevUidCod 설정으로 뺐다. [권한 게이트] E_ShtMst.ShtUsrDesYon — 'Y' 또는 미설정이면 허용, 그 외만 거부(레거시 frmSheetDesigner.vb:71-84 규칙, 거부 문구도 그대로). 레거시는 게이트가 열기 진입점마다 흩어져 있어 이력 노드 클릭·MessageQueue 두 경로로 우회가 가능했는데, SheetMe 는 모든 열기가 OpenDbSheet 로 수렴하므로 거기 한 곳이면 구조적으로 우회가 불가능하다. 추가로 이력 열람과 DB 저장에도 넣었다 — 후자는 레거시에 아예 없던 구멍이다(열기 이후 플래그가 꺼질 수 있고, 파일에서 연 문서를 DB 에 저장하는 경로는 열기 게이트를 거치지 않는다). 목록에서 거르지 않고 잠금 아이콘으로 표시한다(안 보이면 사용자가 진단할 수 없다). --db-gate 진단 신설 — 실측 결과 차단되는 디자인 보유 서식 0건(N 은 1건뿐이고 디자인 없음). [서버 시각] ServerClock 신설(SELECT SYSTIMESTAMP 왕복 1회로 8/12/17자리 확보, 포맷은 invariant 로 C# 에서 — NLS 의존 제거). 감사 정확성뿐 아니라 ResolveActiveSdgKey 가 ORDER BY SdgUpdDtm 으로 활성 버전을 고르기 때문에, 시계가 뒤로 밀린 단말이 저장하면 활성 버전 판정이 뒤집힐 수 있었다. Reorder 는 루프 밖 1회로 바꿔 한 번의 순서 변경이 행마다 다른 시각을 남기지 않게 했다. 실패 시 폴백하지 않는다. RecordWordStore.Remove 에 auditUid 인자 추가 — 남길 컬럼은 없지만(런타임 조회 쿼리에 ShtStt 필터가 없어 논리 삭제로 바꾸면 지운 문구가 계속 노출된다) "모든 쓰기 API 는 uid 를 명시한다"는 규약을 컴파일 타임에 강제한다. 상용구 대화상자는 uid 를 생성자로 받고, 미인증이면 읽기 전용으로 연다. 검증: 사전 실측으로 M_UidMst/M_DepMst/M_HspMst 접근 확인(2단계 최대 리스크 해소, 유효 사용자 539명). 테스트 70/70, edit-smoke 실패 0(기동 인자 파싱 8건 추가), 왕복 1,271건 diff 0/예외 0, db-save-smoke 제자리 갱신 통과, db-word-smoke 통과. DB 직접 확인: P163 저장분 SdgUidCod='SMOKE', SdgUpdDtm 이 서버 시각과 일치 (변경 전 저장분 S999 는 'Msystech' 로 대비됨). 남은 항목: 단일 인스턴스 + Named Pipe IPC 는 미구현. 기동 인자·게이트가 먼저 필요했고, 바로가기 운영에서는 중복 실행 억제가 덜 급하다. 다중 사용자 병행 운영 전에 처리한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5c97bf1440 |
파일 계열 키바인딩 배선 + 미저장 종료 가드
메뉴에는 Ctrl+N/O/S/P 가 표시되지만 Window.InputBindings 가 아예 없어 실제로는 동작하지 않는 잘못된 어포던스였다. 그리고 MainView 에 Closing 핸들러가 없어 타이틀바 X·Alt+F4· 파일 종료 어느 경로로도 미저장 확인이 뜨지 않아, 미저장 다중 문서가 확인 없이 버려졌다. - UndoService 에 savedDepth 기반 IsDirty/MarkSaved 추가. 기존 CanUndo 는 저장해도 영구 더티라 종료마다 무의미한 확인이 떠서 가드가 존재 가치를 잃는다. Undo/Redo 로 저장 지점 깊이에 되돌아오면 자동 clean 이 되고, 저장 지점보다 얕은 상태에서 새 편집이 들어오면 (분기) 되돌아갈 수 없으므로 savedDepth=-1 로 영구 더티 처리한다. 용량 트림 시에도 보정. - OnSaveFile/OnSaveToDb 를 bool 반환으로. 기존 void 는 저장 대화상자 취소를 감지할 수 없어 종료 가드에서 저장 선택지를 줄 수 없었다. 성공 시 MarkSaved() 호출. - ConfirmCloseAll() — 문서별 YesNoCancel 확인. 확인 직전 해당 문서를 활성 탭으로 올려 어느 문서를 묻는지 보이게 한다(저장 경로가 CurrentDesigner 대상이라 전환이 필수다). 과거 버전 열람 탭은 저장하면 활성 디자인을 과거 내용으로 덮으므로 저장 선택지를 주지 않는다. - 종료 경로 두 개를 모두 막는다: MainView.Closing 에서 e.Cancel, 그리고 파일 종료의 Application.Shutdown() 은 e.Cancel 을 무시하므로 VM 이 먼저 확인하고 IsShuttingDown 을 세워 Closing 이 재질문하지 않게 한다. - Window.InputBindings 에는 파일/앱 계열만(Ctrl+N/O/S/Shift+S/P/W). 편집 계열은 CanvasKeyboardBehavior 가 계속 담당한다 — Ctrl+Z/C/V/X/A/D/G 를 창에 올리면 인스펙터· 검색 상자 TextBox 의 표준 편집 키와 충돌하고 Spread 붙여넣기 가드도 우회할 여지가 생긴다. - 저장 진입부에서 포커스가 머문 TextBox 의 BindingExpression.UpdateSource() 호출. 인스펙터 바인딩이 UpdateSourceTrigger=LostFocus 라 값을 고치다 바로 Ctrl+S 를 누르면 커밋되지 않은 채 저장되어 조용히 유실됐다. 검증: edit-smoke 에 더티 추적 8건 추가(초기 clean / 편집 후 dirty / MarkSaved 후 clean / 저장 지점 Undo 복귀 시 clean / 더 되돌리면 dirty / Redo 복귀 시 clean / 분기 후 영구 dirty). 1단계 전체 회귀 통과 — 테스트 70/70, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0, db-save-smoke 제자리 갱신(P163)·버저닝(S999) 양쪽, db-word-smoke 통과. 주의: 키바인딩과 종료 대화상자의 실 UI 조작 확인은 아직 하지 않았다(무인 검증 불가 구간). 병행 운영 전 수동 확인 항목으로 남긴다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
16c07f48dc |
초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너. 기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49. 이 커밋에 함께 포함된 자격증명 분리: - appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환 - 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외, csproj Debug 조건부 복사라 Release 산출물에 실리지 않음) - ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경, 미치환 플레이스홀더는 '미설정'으로 간주 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |