2310971a104cce1cc1fe18cf5bc6e7a310406961
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3ef1937902 |
'새 서식'을 없앤다 — 코드 없는 문서는 그린 뒤에 저장이 막힌다
서식은 항상 목록에서 골라 연다는 결정. ## 없앤 진입점 넷 - 파일 메뉴 '새 서식(N)' - Ctrl+N - 탭 흐름 옆 '+' 버튼(DesignerTheme.xaml) - 빈 화면의 '새 서식' 버튼 시작 시 빈 문서를 만들던 것도 없앴다. 그게 실제 이유다 — 코드 없는 문서는 DB 에 저장할 수 없고(FormId 가 "NewSheet"), 사용자는 한참 그려 넣은 뒤에야 그 사실을 알게 된다. 만들 수 있게 두는 것 자체가 함정이었다. 이제 기동 인자에 서식 코드가 없으면 아무것도 열지 않고 서식 목록 탭을 띄운다. 빈 화면 안내도 '서식 목록에서 열기' 하나로 줄였다(F4 · Ctrl+O 병기). CreateNew 와 파일 저장·열기 코드는 남긴다 — 접속 없이 도는 진단이 그 경로를 쓴다. UI 진입점만 없앤 것이고, 이는 'DB에서 열기' 창을 지울 때와 같은 방식이다. NewFileCommand 를 쓰던 --dialog-shots 는 AttachBlankForDiagnostics 로 바꿨다 (이름에 '진단 전용'을 박아 UI 에서 다시 부르지 못하게 했다). ## 없애자마자 드러난 것 문서가 아예 없는 상태가 <b>기본</b>이 되니 속성 패널에 정렬 바와 간격값이 그대로 남아 있었다. CurrentDesigner.Inspector 가 null 이면 안쪽 Visibility 바인딩이 전부 실패하고, WPF 는 <b>실패한 바인딩을 기본값(Visible)으로 떨군다</b> — Collapsed 가 아니다. 선택이 없다는 안내와 정렬 바가 동시에 떠 있었다. 열린 서식이 없으면 인스펙터를 접는다. 그리고 이 회귀는 값으로 잡히지 않으므로 시각 게이트에 대조군을 넣었다 — 00-main-default(문서 있음 → 인스펙터 보임) 대 00d-main-empty(문서 없음 → 접힘). 대조군 없이 빈 화면만 찍으면 "원래 그랬던 것"과 구분되지 않는다. ## 게이트 - dotnet test 323/323 - --edit-smoke 실패 0 - --dialog-shots FAIL 0 (00d-main-empty 추가) - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9bff3dd5f6 |
MDataTable 을 미리보기에서 실제로 돌린다 — 태그만 채우면 반쯤 빈 것을 못 본다
"태그와 MDataTable도 환자 선택하면 해당 정보로 조회되는지 확인가능해야" 한다는 요구.
확인해 보니 미리보기가 DataTableViewModel 을 아예 건너뛰고 있었다(PrintService:87,143).
서식의 값 상당수가 태그가 아니라 이 관으로 오는데(운영 실측 Rows 형 1,624건)
그쪽은 미리보기에 존재하지 않았다.
## 레거시 구조를 먼저 확인했다
TK_PREVIEW 는 meLoadMode 를 Runtime 으로 바꾸고 TestPatientSetting() 으로
환자 선택 창을 띄운다(frmSheetDesigner.vb:452-462). 고르는 것만으로는 화면이 안 바뀐다 —
moPatInfoBiz 는 필드에 담기기만 하고(:634-674), 서식을 다시 열어야
ucLoadSheetBase(moWrkInfoBiz, moPatInfoBiz, …, EN_LoadType.Edit) 로 로드되면서
그때 태그와 MDataTable 이 실행된다(:216). 탭마다 별도 인스턴스라
환자를 바꿔도 이미 열린 탭은 옛 환자를 계속 들고 있다.
SheetMe 는 1단계다. UsePatient 가 해석기를 갈고 즉시 다시 그린다.
창이 하나뿐이라 잔상도 없다. 이 차이는 의도한 것이다.
## 치환 엔진(QuerySubstitution)
clsMDataTable.ConvertQuery 를 옮겼다. 한 칸이라도 다르면 미리보기가 운영과
다른 SQL 을 돌린다 — "미리보기에서는 나왔는데 실제로는 안 나온다"가 되고
사람이 확인했다고 믿고 넘어가므로 미리보기가 없는 것보다 나쁘다.
처음에 갈래 하나를 반대로 만들었다. 대문자 Item 처럼 정규식이 안 맞는 경우를
"토큰이 남는다"로 단정했는데, GetPropertyInfo 는 정규식 실패에 ""를 돌려주고(:118)
호출부가 그걸 빈 문자열로 치환한다(:66-70) — 즉 지워진다.
토큰이 남는 갈래는 <b>접두어 불일치</b>뿐이다(oBaseObj 가 Nothing 이라 Replace 를 안 한다).
소스를 읽어 고쳤다. 두 갈래를 섞으면 안 되는 이유는 하나는 ORA 구문오류가 되고
다른 하나는 조건이 사라진 SQL 이 조용히 도는 것이라서다.
SQL 에서는 같아지는 것들도 사람에게는 갈라서 말한다 —
Unknown(아직 안 옮긴 속성) / Empty(값이 빔) / NotAVariable(접두어 틀림).
셋은 고칠 곳이 전부 다르다.
## 짐작하지 않는 변수원(PatientQueryVariableSource)
DataRow 접근형이 이 기능의 대부분을 실어 준다 — PatInfDR.item("아무컬럼") 은
SELECT * 결과 사전을 그대로 조회하면 되므로 <b>내가 컬럼을 알 필요가 없다.</b>
짐작할 것이 없으니 조용히 틀릴 일도 없다.
스칼라도 대부분 그 다섯 행의 컬럼 하나라, 컬럼 이름만 적고 있으면 값·없으면 모른다고
답한다. 존재 여부는 DB 가 판정한다. UDF 산출값(Age·Sex)은 넣지 않았다.
## 값이 안 나올 때를 위한 창
종이에는 "[MDataTable1.ALGYON — 조회 결과가 0행입니다]" 한 줄만 나온다.
그것으로는 쿼리가 틀렸는가·치환이 빈 값이 됐는가·이 환자에게 자료가 없는가를 못 가른다.
그래서 데이터소스 창이 <b>치환을 마친 SQL</b>을 그대로 보여 준다.
값이 안 나왔을 때 봐야 하는 것은 결과가 아니라 무엇을 물었는가다.
레거시에는 이걸 볼 수단이 없었다 — 런타임이 빈 catch 로 삼켜 "빈칸"만 남았다.
진단 화면 표본도 <b>실패 상태</b>로 찍는다. 성공 화면만 회귀 대상으로 두면
정작 사람이 오래 들여다보는 화면이 검사에서 빠진다.
## 안전
- 실행은 OracleQueryWorkbench.Trial 을 그대로 쓴다 — SELECT/WITH 문이 이미 거기 있다.
문을 두 군데 두면 한쪽이 느슨해진다.
- 치환 값은 레거시처럼 원문 그대로 박힌다(그래야 같은 SQL 이다). 따옴표가 섞인 값은
QuotedValues 로 드러내고 실행은 SELECT/WITH 문이 막는다.
- 데이터소스당 한 번만 실행하고 캐시한다 — 배선 40개면 왕복 40회가 된다.
- 환자를 바꾸면 러너를 새로 만든다. 재사용하면 태그만 바뀌고 표는 옛 환자가 남는데
그건 아무도 눈치채지 못한다. 서식을 갈아탈 때도 다시 만든다(옛 서식의 쿼리를 쓰게 된다).
## 아직 안 되는 것
Select 형의 필터(DataTable.Select 메모리 문법)는 옮기지 않았다.
무시하고 행 번호만 쓰면 다른 행의 값이 조용히 찍히므로, 값을 내지 않고 사유를 말한다.
운영 다수파인 Rows 형(76%)은 된다.
## 게이트
- dotnet test 323/323 (치환 판정 10건 신규)
- --edit-smoke 실패 0 (배선 판정 7건 추가, 대조군 포함)
- --db-patient ①~⑱ 전건 통과. ⑯ 치환한 SQL 이 실제로 1행을 뽑고,
⑱ 대조군은 환자 없이 같은 쿼리가 "WHERE ComNum = AND ... = ''" 로 깨진다 —
이 대조가 없으면 ⑯ 은 "쿼리가 원래 환자와 무관했다"와 구분되지 않는다
- --dialog-shots FAIL 0 (08c-datasource-result 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
- --db-smoke 1,271건 diff 0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
88dab2edcf |
환자를 골라 태그에 값이 들어간다 — 데이터 계층만 있던 것을 화면에 붙인다
전 커밋까지 환자 조회·시각 판정·권한 규칙은 다 있었지만 화면에 붙은 것이 하나도 없었다. 미리보기를 열면 환자 선택이 없고, 태그는 전부 사유만 찍혔다. ## 만든 사슬 PatientPickerDialogView(모달) → PatientContextStore(문맥 5행) → PatientTagResolver(태그 → 컬럼) → PreviewWindow.UsePatient → 종이 모달로 둔 이유: 30초 안에 끝나는 자족적인 일이고 권한 문이 걸린 동작이다. 사이드 패널로 두면 실제 환자 정보를 꺼내는 창이 항상 열려 있게 되는데 이 창은 열려 있는 것 자체가 비용이다. 환자와 내원을 한 화면에 둔다. 고르는 대상은 환자가 아니라 내원이다 — 문맥 5행이 전부 ComNum + AdpDtm 으로 읽히므로 환자만 골라서는 아무것도 못 읽는다. 기준 시점(마지막/처음)을 화면에 노출한다. 입원 내원의 진료과·병실·자격 값을 실제로 바꾸는 값이라 숨기면 값이 왜 다른지 설명할 수 없다. 문맥 읽기를 창 안에서 한다. 닫고 나서 읽으면 실패를 미리보기 창에서 알려야 하고 그때는 이미 고른 것이 사라진 뒤다. ## 매핑은 확인된 6종만 PAT_이름 / PAT_차트번호 / PAT_주민번호 / PAT_휴대전화 / PAT_전화번호 / PAT_주소. 전부 레거시 조회 SQL 의 SELECT 목록에서 컬럼을 직접 확인한 것이다. PAT_나이·PAT_성별은 넣지 않았다 — UDF_GETAGE·UDF_GETSEX 를 거치는 값이라 컬럼을 짐작해 넣으면 값이 조용히 틀린다. 종이에 그럴듯한 값이 찍히면 아무도 의심하지 않으므로 빈칸보다 나쁘다. 안 옮긴 환자 태그는 "아직 옮기지 않았습니다"라고 말한다. "DB 접속이 없습니다"로 뭉개면 환자를 골랐는데도 그 사유가 나와 접속을 의심하게 된다. ## 문맥 조회를 한 번에 다섯 번 읽는다 레거시는 5행을 프로퍼티 접근 시점에 하나씩 지연 로딩한다(각각 별도 왕복). 태그마다 다시 읽으면 태그 40개짜리 서식에 왕복 40회가 된다. ## 개인정보 - 감사 로그는 고른 직후 남긴다 — 종이에 값이 찍히는 것과 무관하게 조회는 이미 일어났다. - 로그에 환자 이름을 남기지 않는다(내원번호로 추적 가능하고, 로그가 개인정보를 들고 있으면 로그 자체가 위험물이 된다). - 진단 리포트도 값 대신 길이만 적는다. - 권한 문은 수정(TK_MODIFY)과 같은 범위다. 넓히면 서식생성기가 환자 조회 도구가 된다. - 화면에서 감사 기록 사실을 말한다. ## 게이트가 잡은 것 셋 ① --db-patient 가 "전건 통과"인데 ⑧~⑬ 이 통째로 SKIP 이었다. 첫 환자에게 내원이 0건이라 문맥 조회를 한 번도 안 돌린 채 초록불이었다. 내원 있는 환자를 30명까지 훑고, 못 찾으면 SKIP 이 아니라 FAIL 로 바꿨다. ② 그렇게 찾은 내원에서 CodInf·CoiInf·CowInf 가 셋 다 0행이었다. 0 을 적어 두는 것으로는 "조건이 어긋났다"와 "행이 없다"를 못 가른다. 조건 없는 원본 행을 함께 찍게 하니 후자였다(1988년 내원, 행 자체가 없음). ③ 그러면 기간 조회 세 갈래는 아직 한 번도 행을 돌려준 적이 없다. 안 돌아본 갈래는 통과한 갈래가 아니므로, 각 표에서 직접 내원을 찾아 들어가 BETWEEN·MtiSeq 조건이 실제로 행을 뽑는 것까지 확인하게 했다(⑮). --edit-smoke 판정 7건 추가. 전부 대조군이 있다 — 환자 없이 같은 종이를 찍어 사유가 나오는 것을 먼저 확인한 뒤 값으로 바뀌는 것을 본다. 미리보기 창 배선도 따로 본다(붙이면 값, 떼면 사라짐). 그래서 고르는 일과 붙이는 일을 나눠 두었다 — 모달에 묻어 두면 "골랐는데 종이가 그대로"를 자동으로 못 잡는다. ## 게이트 - dotnet test 312/312 - --edit-smoke 실패 0 (환자·미리보기 판정 7건 추가) - --dialog-shots FAIL 0 (08b-patient-picker 추가) - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 (변화 없음) - --db-smoke 1,271건 diff 0 - --db-patient ①~⑮ 전건 통과 (기간 조회 3갈래 실제 통과 확인) - --db-modify-smoke S999 8/8 (⑤ 는 E_SctMst 0행으로 여전히 SKIP) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a386c5504b |
열기·저장을 DB 로만 — 'DB에서 열기' 대화상자를 지우고 좌측 목록으로 모은다
<b>먼저 밝힐 것: 내가 "완전 중복"이라고 인용한 판정이 틀렸다.</b> 좌측 목록과 이 대화상자는 같은 조회를 쓰지만(둘 다 ListSheets, WHERE·ORDER BY·상한 동일) 같은 쓸모를 주지 않았다. 지우기 전에 구멍 넷을 좌측에 옮겼다. <b>① 저장·등록 뒤 재조회 — 가장 컸다.</b> 좌측 목록은 기동 1회와 검색 버튼으로만 채워졌고 저장 뒤 갱신이 없었다. 즉 <b>방금 저장한 서식이 목록에 안 떴다</b>. 대화상자는 열 때마다 재조회해서 그 구멍을 덮고 있었다. 그것을 안 메우고 창만 지웠으면, DB 전용으로 바꾼 직후 신규 서식을 저장한 사용자가 그 서식을 다시 찾지 못했을 것이다. <b>② 서식명 툴팁</b> — 좌측은 폭이 148px 남짓(자물쇠 붙으면 134, 패널 최소폭이면 80)이라 긴 이름이 잘리는데 전문을 볼 방법이 없었다. 대화상자는 열이 300px 에 드래그로 늘릴 수 있었다. <b>③ '디자인 없음'과 '서식생성기 사용 꺼짐'</b> 이 좌측에서는 둘 다 같은 회색+Opacity 0.6 이라 화면만 보고 구분할 수 없었다. 툴팁으로 갈랐다. <b>④ Enter 로 열기</b> — 좌측은 더블클릭 전용이라 키보드로 서식을 열 수 없었다. 대화상자에는 [열기] 버튼이 있어 그 구멍이 가려져 있었다. 검색칸에서 ↓ 로 목록에 내려가는 길도 붙였다. 그 다음 지웠다: SheetOpenDialogView(680×560), OpenFromDbCommand, 파일 메뉴의 파일 열기· 다른 이름으로 저장·DB에서 열기, 빈 화면의 '파일 열기' 버튼, Ctrl+Shift+S. Ctrl+O 는 이제 서식 목록으로 간다 — 전에는 OS 파일 대화상자였고, 운영 서식은 전부 DB 에 있다. <b>SaveMode 기본값은 바꾸지 않았다.</b> 조사해 보니 그 규칙은 실재하고 문서·코드 4곳에 있다 — docs/DEPLOYMENT.md:56 은 "이 기본값을 바꾸지 말 것"이라고 명시한다. 배포된 ini 의 CurrentServer 가 운영 병원 DB 를 가리켜서, 명시적으로 켠 단말에서만 운영 테이블(E_SdgMst/E_SctMst)에 쓰게 하려는 장치다. ini 는 SaveMode 를 담지 않으므로 이 규칙이 병원 단말의 유일한 방어선이다. UI 진입점만 없애고 규칙은 그대로 둔다. 파일 저장·열기 <b>코드</b>도 남긴다 — 접속 없이 도는 --render-smoke 와 --edit-smoke 직렬화 왕복이 그 경로를 쓴다. 대신 화면이 상태를 말하게 했다 — SaveMode=File 단말에서는 저장 메뉴·버튼이 비활성이다. 전에는 항상 활성이고 누른 뒤에야 경고가 떴다. <b>종료 가드를 고쳤다 — 앱이 안 닫히는 상태를 없앤다.</b> 전에는 저장 실패가 곧 종료 중단이었다. 파일 폴백이 있을 때는 드문 분기였지만 저장이 DB 로만 가면 흔한 경로가 된다 — SaveMode=File 단말이나 접속 불가에서 <b>수정한 탭을 가진 사용자가 앱을 닫을 수 없게</b> 된다. 이제 '버리고 종료'를 묻는다. 게이트 대체물도 함께 옮겼다: --dialog-shots 의 01-sheet-open 을 지우면 서식명 잘림을 잡을 자리가 통째로 사라지므로, 좌측 목록에 표본을 넣은 01-sheet-list 샷으로 바꿨다 (진단 모드는 Loaded 를 안 돌려 목록이 비므로 표본을 직접 주입한다). 검사한 글자 2,531 → 2,649개. 게이트: 테스트 291/291, --edit-smoke 0실패, --dialog-shots 62장 FAIL 0 대조군 4/4, --modal-check 0실패, --cleartype 11/11, --maxrect 0실패, --scale-budget 5/5, 빌드 경고 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> |
||
|
|
862657cf2f |
글자가 잘리면 CI 가 실패하게 만든다 — 사람이 그림 57장을 보는 대신
재설계 1단계. 화면 픽셀은 하나도 안 옮긴다. 지금까지 이 저장소에서는 글자가 잘렸다는 이유로 실패할 수 있는 것이 하나도 없었다. 테스트 277건은 Core·Data 만 참조해 XAML 을 한 줄도 로드하지 않고, --dialog-shots 는 PNG 56장을 만들면서 단정하는 것은 창 배경색과 스타일 존재 여부 둘뿐이다. 그래서 이번 세션에만 세 건이 눈으로만 잡혔다 — "232 로는 이름이 잘린다", "220 이면 마지막 토글이 잘린다", "0 크기 Grid 가 자식을 폭 0 으로 재서 글자가 사라진다". 글자를 담은 요소만 본다. WPF 에서 DesiredSize > RenderSize 는 정상인 경우가 많아 전부 훑으면 수백 건이 떠서 게이트가 무용지물이 된다. 측정이 검사 방식을 두 번 정정했다. ① "Width 를 좁게 주고 말줄임표를 끄면 잘린다"고 놓았는데 아니었다. TextTrimming=None 이면 TextBlock 은 준 Width 를 지키지 않고 자연폭을 그대로 ActualWidth 로 보고한다(Width=40 인데 ActualWidth=213.3). 즉 WPF 에서 말줄임표 없는 NoWrap 글자는 잘리는 것이 아니라 <b>옆을 침범한다</b>. 그래서 자기 ActualWidth 와 비교하는 대신 <b>자르는 조상의 사각형</b>과 비교한다. 자르는 조상이 없으면 위반으로 세지 않는다 — 겹침은 다른 종류의 결함이고 이 검사기가 판정할 수 있는 것이 아니다. ② 0 폭 칸에 갇힌 글자는 ActualWidth 가 0 이 아니라 9.6 이었다(말줄임표 글리프 몫). 대조군 기대값을 그에 맞게 고쳤다. 자가 점검을 함께 넣었다. 위반 0건은 "화면이 깨끗하다"와 "검사기가 고장났다"를 구분해 주지 않고, 보고서에서 그 둘은 똑같이 생긴다. 이번 세션에 그 함정에 두 번 빠졌다 — --maxrect 가 기본값 0 을 기대값 0 과 비교해 공허하게 통과했고, --modal-check 가 Loaded 보다 먼저 재서 언제나 0 을 셌다. 그래서 일부러 잘린 대조군 4종을 넣고, 검사한 글자 수를 함께 적는다. 실측: 대조군 4/4 · 창 57개에서 글자 1,936개 검사 · 넘침 0건. 기동 화면 밖 문제는 최우선에서 내렸다 — 현장 단말이 1920x1080 이상뿐이라는 답을 받았다. 1440x920 을 1920x1032 에 CenterScreen 하면 Left 240 / Top 56 으로 양수다. MaximizeToWorkArea.cs:251 의 1366x768@125% 주석은 현장 기술이 아니라 방어적 가정이었다. 같은 이유로 A4 맞춤은 50% 가 아니라 77% 이고, 화면 예산 회수는 급한 일이 아니다. 게이트: 테스트 277/277, --dialog-shots 대조군 4/4 넘침 0, --cleartype 11/11, --edit-smoke 0실패, --modal-check 0실패, --maxrect 0실패, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
26f696ab00 |
정렬 — 기준은 언제나 '담고 있는 것'. 그리고 속성 패널에 정렬 행
"폼 기준으로 정렬"이 없었다. 정확히는 정렬 자체가 2개 이상에서만 동작했고 기준은 늘 선택 영역이라, 컨트롤 하나를 페이지 가운데 놓을 방법이 메뉴 어디에도 없었다. 규칙을 한 문장으로 바꿨다 — 기준은 그것을 담고 있는 것이다. 선택이 2개 이상이면 선택 영역이 그 자리를 대신하고, 1개면 담고 있는 것 — 컨테이너 안이면 컨테이너, 최상위면 페이지 — 이 기준이 된다. 피그마와 같다. 컨테이너 자식을 페이지 기준으로 맞추지 않는 이유. X/Y 는 부모 상대좌표라 '페이지 왼쪽'에 맞추면 음수가 되어 부모 밖으로 나간다. 디자이너 캔버스는 클리핑을 안 해서 멀쩡히 보이지만 레거시 런타임에서는 부모가 잘라먹는다 — 화면과 인쇄가 갈라지고 눈으로는 못 잡는 종류의 버그다. 같이 고친 것들: · 페이지를 넘는 선택의 세로 정렬을 거부한다. 다른 종이의 '위 맞춤'에는 뜻이 없고, 예전에는 실제로 깨졌다 — bbox 는 첫 페이지 월드 Y 인데 offset 은 자기 페이지 OffsetY 라 vm.Y 가 큰 음수가 되어 컨트롤이 제 종이 위 허공으로 날아갔다. 가로는 모든 페이지의 X 원점이 0 이라 그대로 허용한다. · 부모와 자식을 함께 선택한 경우 자식을 대상에서 뺀다. 부모를 옮기면 자식 월드 좌표가 따라 움직이는데 자식에게도 절대좌표를 대입하면 두 번 옮겨진다(레이어 목록에서 만들 수 있는 선택이다). · 반올림을 AwayFromZero 로 고정했다. 기본 ToEven 은 322.5→322, 323.5→324 로 방향이 값에 따라 뒤집힌다 — 폭이 제각각인 라벨 다섯 개를 가운데 맞춤하면 어떤 건 322, 어떤 건 323 에 앉는다. (스냅에서 반올림을 뺀 것과 반대로 보이지만 이유가 다르다. 거긴 가이드선이 모서리에서 뜨는 게 보이기 때문이고, 정렬은 선을 긋지 않는다. 저장 포맷은 어차피 정수다.) · 변경이 실제로 생길 때만 실행취소를 찍는다. 버튼으로 노출되면 연타되는데, 무조건 찍으면 빈 되돌리기 단계가 쌓이고 '저장 안 한 변경'으로 표시된다. 산술은 Core/Layout/AlignSolver 로 뺐다. 짧지만 경계가 잔소리 같은 규칙 덩어리라(반올림 방향, 기준보다 넓은 항목, 원점이 큰 둘째 장) 디자이너 쪽에서 검사하면 케이스마다 실행취소 스냅샷을 하나씩 먹는다. 단위 테스트 10건이 그걸 공짜로 고정한다. 속성 패널 정렬 행. [200]SheetMe 의 inspector-wireframe.html 을 따랐다 — 30x26 버튼 6개, radius 6, 사이 2px, 가로 축과 세로 축 사이 8px 틈, 아이콘은 stroke 1.5. LucideIcons 의 align-obj-* 6개는 등록만 되고 아무 데서도 안 쓰이던 죽은 항목이라 참조 도형으로 갈아 끼웠다(사각형 둘 + 기준선 → 사각형 하나 + 기준선). 좌표는 24격자다 — Build 가 Canvas 를 24x24 로 하드코딩해서, 참조의 16격자 좌표를 그대로 넣으면 아이콘이 좌상단 2/3 크기로 쏠린다. 크기 18 에서 stroke 2 × 18/24 = 1.5px 로 참조와 같은 굵기가 나온다. 버튼 스타일은 새로 만들었다. SegmentToggle 은 치수가 같지만 채워진 상태 토글 룩이고, 정렬은 상태가 아니라 액션이라 눌려 있는 것처럼 보이면 안 된다 — 채움 없이 호버만 남겼다. 표시 조건에 HasSelection 을 쓰지 않았다. 그건 페이지 속성 모드에서도 true 라 정렬할 컨트롤이 없는데 바가 뜬다. 선택 0 에서는 접어야 하는데, 안 접으면 '컨트롤을 선택하면…' 안내문 뒤에 버튼 줄이 그대로 남는다(같은 Grid 에 겹쳐 그려진다). 툴팁이 지금 무엇을 기준으로 정렬하는지 말한다 — "왼쪽 맞춤 — 페이지 기준" / "— Panel1 기준" / "— 선택 영역 기준". 세로 3버튼은 페이지를 넘는 선택에서 비활성되고 그 이유를 툴팁에 적는다. 아이콘만 있는 버튼이라 AutomationProperties.Name 을 붙였다 — 이 저장소에 버튼 자동화 속성 전례가 없어 새로 여는 관례다. 메뉴는 그대로 두되 툴팁으로 전제를 적었고, '페이지 가로/세로 가운데'는 헤더에 '묶음째'를 붙였다. 선택 1개에서는 CenterH 와 결과가 같아지고, 다중에서만 갈리기 때문이다(그건 상대 배치를 보존한 채 묶음을 옮기고, CenterH 는 각자 가운데로 모은다). 분배(간격 균등) 버튼은 넣지 않았다. 참조 와이어프레임에 없고, 3개 이상에서만 살아서 실사용 대부분(1~2개 선택)에 두 칸이 죽은 채 남아 줄이 고장 난 것처럼 읽힌다. --dialog-shots 에 선택 상태별 인스펙터 3장(12c/12d/12e)을 더했다. 정렬 바가 선택 0 과 페이지 속성 모드에서 사라지는지는 수치로 못 잡는다. 산출물 51 → 57. 기존 인스펙터 5장은 정렬 행만큼 아래로 밀린다 — 정상 diff 다. 알려진 별건(이번에 안 고침): 정렬 후 Ctrl+Z 하면 선택이 풀린다. Undo 가 문서를 통째로 갈아끼우며 Selection.Clear 하기 때문이고, 정렬은 '왼쪽 → 아니네 → 가운데'로 여러 번 시도하는 조작이라 버튼으로 노출되면서 드러난다. Id 기반 선택 복원이 필요해 별도로 다룬다. 게이트: 테스트 267/267(신규 10), --edit-smoke 234건 0실패(정렬 검사 11건 신규), --query-popup 17/17, --maxrect 10/10, --snap-shots 7종 0실패, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일, --dialog-shots 57파일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
749de6b566 |
쿼리 편집기 세 가지 — 한글이 사라지던 것, 줄 번호가 밀리던 것, 클릭이 먹지 않던 것
1) 한글로 입력하면 글자가 안 보였다. 색칠은 "투명한 TextBox 뒤에 우리가 그린다"로 되어 있다. 그런데 IME 조합 중인 글자는 아직 TextBox.Text 에 없다 — TextChanged 가 오지 않으니 우리는 그릴 수 없고, TextBox 는 제 Foreground(투명)로 그리니 아무것도 안 보인다. 자음·모음을 맞추는 내내 빈 화면이었고, 확정하고 나서야 나타났다. 조합이 시작되면 TextBox 에게 잉크색을 돌려주고 색칠 층을 감춘다(ShowRawText). 확정되면(TextInput) 되돌리고, ESC 로 취소돼 CompositionText 가 비어도 되돌린다. 조합 도중 포커스를 잃으면 끝 신호가 오지 않으므로 blur 에서도 되돌린다 — 색 없는 상태로 굳지 않게. 조합 중 잠깐 단색이 되는 편이 글자가 안 보이는 것보다 낫다. 2) 줄 번호가 실제 줄과 어긋났다. 번호를 "1\n2\n3…" TextBlock 한 덩어리로 두면 번호의 줄 간격은 TextBlock 조판이, 본문의 줄 간격은 TextBox 가 정한다. 한글 글꼴 대체가 끼면 조금씩 벌어져 아래로 갈수록 눈에 띄게 밀렸다. LineNumberGutter 를 만들어 각 줄 첫 글자의 y 를 GetRectFromCharacterIndex 로 물어 그 자리에 번호를 그린다. 스크롤·여백·폰트 대체가 전부 TextBox 기준으로 반영돼 오므로 구조적으로 어긋날 수 없다. 폭은 가장 큰 번호에 맞춰 잡고 번호는 오른쪽 정렬한다. 3) 자동완성 목록의 항목을 클릭하면 아무것도 안 들어가고 목록만 닫혔다. Popup 이 StaysOpen=False 라 마우스를 캡처하고 있었다. 항목을 누른 그 클릭이 '바깥 클릭'으로 먼저 소비돼 팝업이 닫히고, 항목은 클릭을 받지 못했다. StaysOpen=True 로 바꾸고 닫는 일은 코드가 맡는다(커서 이동·포커스 상실·창 비활성 — 이미 있던 핸들러들). 집는 처리는 팝업 안쪽 Border 에서 터널 단계로 받는다. 진단(--query-popup)에 "목록 항목을 집으면 입력되고 목록이 닫힌다"를 더해 17건. 마우스 라우팅 자체는 화면 없이 흉내 낼 수 없어(Ctrl+Enter 때와 같은 이유) 핸들러가 부르는 경로를 그대로 태워 삽입까지 확인한다. IME 조합은 합성할 수 없어 1) 은 실기 확인이 필요하다. 게이트: 테스트 230/230, --edit-smoke 0건, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f447ae742b |
검증 실행 결과에 출력 행이 안 보이던 문제
결과를 ExpandoObject 목록으로 DataGrid 에 넘기고 있었다. DataGrid 의 AutoGenerateColumns 는
항목 <b>타입</b>의 속성을 반사해 열을 만드는데 ExpandoObject 에는 그런 속성이 없다 —
그래서 요약("성공 — 컬럼 2개, 표본 3행")은 맞게 뜨는데 표는 비어 있었다.
DataTable 로 바꿨다. DataGrid 가 직접 알아본다.
같은 컬럼명이 두 번 나오면(별칭 없는 조인에서 흔하다) DataTable 이 예외를 내므로 뒤에 번호를 붙인다.
■ 0행과 실패를 구분한다
구문은 맞는데 조건에 걸리는 데이터가 없을 수 있다. 검증 실행은 치환 변수를 빈 값으로 바꿔
돌리므로(런타임과 같은 방식) 조건이 빡빡하면 0행이 정상이다.
그 경우 표를 감추고 "구문은 정상입니다 — 치환 변수를 빈 값으로 실행하므로 조건에 걸리는 행이
없을 수 있습니다" 를 덧붙인다. 빈 표를 보여 주면 실패한 것처럼 읽힌다.
결과 영역을 190 → 260px 로 키웠다.
■ 이 종류를 다시 놓치지 않도록
--query-popup 진단에 3건을 더 넣었다: 결과 창이 열리는가 · 표에 열이 생기는가 · 행이 채워지는가.
요약만 검사했다면 이번 결함을 그대로 통과시켰을 것이다. 현재 12/12.
--dialog-shots 에도 검증 실행을 마친 상태(03b-query-trial)를 추가해 눈으로도 남는다.
회귀: 테스트 230/230, 편집 스모크 실패 0, 팝업·정렬·결과표 점검 12/12,
검증 실행 점검 10/10, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
68fc01c6ba |
쿼리 편집기 레이아웃 — 카드 두 장, 갈래 칩, 그리고 《컨트롤명》 치환
목업대로 다시 짰다. 색·모서리·간격은 SheetMe 토큰을 그대로 쓴다. ■ 레이아웃 머리말(제목 + 대상 칩) / SQL 편집기 카드 / 치환 변수 카드 / 아래 버튼 줄. 편집기 카드에는 툴바(포맷·주석·자동완성 · 전체 지우기)와 상태 줄(연결 상태 · 글자·줄 수 · 줄,열)을 뒀다. 변수 카드에는 검색 + 갈래 칩 + 접이식 그룹 + 행마다 + 버튼을 뒀다. + 버튼을 넣은 이유는 더블클릭이 유일한 삽입 수단이었기 때문이다 — 화면에 드러나지 않는 조작이다. ■ 《컨트롤명》 치환 — 아예 없던 기능 레거시 화면에는 Controls 목록이 따로 있었다. 우리에겐 그 문법 자체가 없었다. clsMDataTable.ConvertQuery 는 <<…>> 를 모두 치환한 뒤 《 로 다시 쪼개 poAllControlOnSheet(이름).GETVALUE 를 박는다(:78-94). 즉 같은 서식의 다른 컨트롤 값을 쿼리 조건에 넣을 수 있다. 이제 이 서식의 컨트롤이 '컨트롤' 갈래로 목록에 뜨고, 토크나이저도 《》 를 변수로 잡아 색이 붙고 검증에 걸린다. 다만 디자인 시점에는 컨트롤 목록이 없어 런타임이 무조건 "0" 으로 치환한다(:86) — 검증 실행이 통과해도 운영에서는 다른 값이 들어간다는 뜻이라 코드에 적어 뒀다. ■ 형식(String/Int32/DataRow) 표시 레거시가 '목록/형식' 두 열로 보여 주던 정보다. DataRow 인지 아닌지가 곧 '컬럼명을 더 채워야 하는가'를 뜻해서 고를 때 필요하다. ■ 갈래 칩 그룹이 일곱이라 칩으로 다 늘어놓으면 그것대로 목록이 된다. '값이 어디서 오는가'로 네 갈래(환자·서식·컨트롤·작업자)로 묶고, 같은 칩을 다시 누르면 해제된다. ■ 포맷(Ctrl+Shift+F 상당 — 툴바 버튼) 예약어·함수를 대문자로 바꾸고 주요 절 앞에서 줄을 나누며 괄호 깊이만큼 들여쓴다. 문자열·주석·치환 변수는 손대지 않는다 — 특히 치환 변수는 대소문자가 맞아야 런타임이 찾으므로 대문자로 바꾸면 그 칸이 조용히 빈다. 이 포매터가 저지를 수 있는 가장 나쁜 일이라 테스트로 막았다. 두 번 눌러도 결과가 같다(안정성도 테스트로 고정). 단위 테스트 14건 추가(포매터 11 · 컨트롤 치환 3). 회귀: 테스트 230/230, 편집 스모크 실패 0, 팝업 점검 7/7, 검증 실행 점검 10/10, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
146d808aac |
태그 피커 재설계 — 찾기(초성·건수)와 둘러보기(분류)를 함께
칩으로 자주 쓰는 것을 앞에 깔았지만, 피커 자체는 여전히 384줄짜리 평면 목록이었다.
목록에 구조가 없으면 무엇이 있는지 알 길이 없어 결국 아는 이름만 반복해 쓰게 된다.
■ 분류 — '무엇이 있는지'
태그 이름에는 이미 구조가 있다(PAT_ 환자 · OCM_ 진료·오더 · ETC_ 기관·문서).
그 구조를 왼쪽 분류 목록으로 드러냈다. 건수와 함께 보이므로 규모가 잡힌다:
환자 40 · 진료·오더 260 · 기관·문서 75 · 그 밖 9.
가상 분류 셋을 위에 뒀다:
· 자주 쓰는 것 — 이 컨트롤 타입의 실사용 상위(타입마다 다르다)
· 운영에서 쓰임 157 — 384종 중 실제로 한 번이라도 쓰인 것만
· 전체 384
아는 접두어만 분류로 올리고 나머지는 '그 밖'으로 묶는다. 레거시에는 규약에서 벗어난 이름이
섞여 있어서(오타 ECT_로그인사용자싸인 — bzDataInterface.vb:17850 의 실제 오타다,
접두어가 아예 없는 GetNurseShtCod) 접두어를 그대로 분류로 쓰면 1~3개짜리 분류가
목록을 채워 '무엇이 있는지'를 오히려 가린다.
■ 사용 건수 — '이게 표준인가'
이름만으로는 PAT_주민번호 와 PAT_주민번호_Dash 중 무엇이 표준인지 알 수 없다.
운영 실측(--db-tags)을 줄마다 배지로 띄운다(PAT_이름 669, PAT_차트번호 463 …).
한 번도 안 쓰인 태그는 '미사용'으로 표시한다 — 그것도 정보다.
정렬은 자주 쓰는 순 → 운영 건수 → 이름. 가나다순은 운영 절반을 차지하는 태그와
한 번도 안 쓰인 태그를 같은 무게로 보여 준다.
■ 초성 검색 — 손이 덜 가게
한글 태그 이름이 길어 전체를 칠 이유가 없다. "ㅊㅌㅂㅎ" 네 글자로 PAT_차트번호 에 닿는다.
입력이 초성만으로 이뤄졌을 때만 초성 대조를 시도한다 — 일반 검색어를 초성으로 오인하면 안 된다.
공백으로 나눈 여러 토큰은 모두 만족해야 하고(AND), 일반어와 초성을 섞어도 된다("OCM ㅈㄷㅅ").
■ 그 밖
· 검색칸에서 ↑↓·PageUp/Down 으로 바로 목록을 움직인다 — 검색하고 Tab 으로 옮겨 가는 왕복을 없앴다.
· 현재 값이 있으면 그 값이 속한 분류로 열고 그 줄을 선택해 둔다.
· 검색으로 지금 분류가 비면 결과가 있는 분류로 옮겨 간다(빈 화면을 보여 주지 않는다).
· 창을 720×620 으로 키우고 크기 조절을 열었다. 선택된 값을 하단에 항상 띄운다.
· AutomationProperties.Name 을 붙였다(사내 UI 가이드 접근성 항목).
사내 UI 가이드의 판단 기준을 따랐다 — '빈도 높은 동작에 모달을 쓰지 않는다'.
자주 쓰는 태그는 인스펙터 행의 칩으로 이미 빠져 있어 이 창을 열 필요가 없고,
이 창은 '둘러보기' 전용으로 남긴다(그 용도에는 집중 화면이 맞다).
단위 테스트 20건 추가(분류 매핑·규약 밖 이름 접기·초성 추출·초성 판정·부분 일치·
다중 토큰 AND·변형 묶기·건수 스냅샷).
회귀: 테스트 162/162, 편집 스모크 210건 실패 0,
DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
de01012b7e |
태그 선택 — 타입별로 자주 쓰는 것을 그 자리에 깐다
태그를 하나 걸려면 매번 모달을 열어 500여 개(액션 160여 개) 목록에서 찾아야 했다. 한 서식이 쓰는 서로 다른 태그는 중앙값 6개, 최대 27개다(--db-tags) — 서식 하나 만들 때마다 그 짓을 예닐곱 번 한다. ■ 실측이 설계를 정했다 --db-tags 진단을 신설해 운영 디자인 1,271건의 태그 사용 8,032건을 컨트롤 타입별로 집계했다. 후보가 타입마다 거의 완전히 갈린다: 이미지 · 데이터 태그 343건 / 7종 — 상위 16종이 100% (서명·직인·로고가 전부) 라디오 · 액션 태그 736건 / 7종 — 100% (RadCheckControl 이 580건) 체크박스 · 액션 태그 357건 / 7종 — 100% 텍스트박스 · 액션 태그 200건 / 6종 — 100% 마스크입력 · 데이터 태그 57건 / 13종 — 100% 버튼 · 액션 태그 393건 / 46종 — 상위 16종이 83% 텍스트박스 · 데이터 태그 5,927건 / 144종 — 상위 16종이 65% (여기만 꼬리가 길다) 즉 일곱 조합 중 다섯은 **짧은 목록이 운영 전체를 덮는다**. 이미지 하나 고르려고 500개를 뒤지고 있었다는 뜻이다. ■ 바꾼 것 · 인스펙터 행 아래에 그 타입에서 자주 쓰는 태그를 빈도순 칩으로 깐다(최대 8개). 누르면 바로 값이 된다. 칩은 **값이 비어 있을 때만** 뜬다 — 이미 정해진 행 아래에도 깔면 좁은 패널이 금방 길어진다. · 피커를 열면 자주 쓰는 것이 맨 위로 올라온다(그 안에서는 빈도순, 나머지는 원래 가나다순 유지). 건수 표시에 '자주 쓰는 것 N건이 위에' 를 덧붙였다. · 전체 카탈로그와 자유 입력은 그대로다. 제안은 제안이지 제한이 아니다 — 타입별 자료가 없는 조합의 액션 태그는 아예 비워 둔다. 추측으로 권하면 그 칸은 배포된 뒤에야 빈칸으로 드러난다. 순서를 가나다순이 아니라 빈도순으로 둔 것이 핵심이다. 가나다순은 운영의 절반을 차지하는 RadCheckControl 과 한 번도 안 쓰인 태그를 같은 무게로 보여 준다. ■ 곁다리로 확인한 것 카탈로그에 없는 값이 데이터 태그 1종(PAT_전체주소 ×11), 액션 태그 1종(OprEmrList ×5) 나왔다. 둘 다 레거시 bzDataInterface / EN_DataActionTyp 에 존재하지 않는다 — 우리 카탈로그가 맞고, 저 값들은 운영 서식에 이미 박힌 죽은 태그다(런타임이 해석하지 못한다). 우리가 만든 것이 아니라 손대지 않았다. 편집 스모크 17건 추가(타입별 좁힘 / 빈도 순서 / 자료 없는 타입은 빈 목록 / 제안이 전부 카탈로그에 있는지 12조합 검사 / 값이 정해지면 칩 감춤). 회귀: 테스트 142/142, 편집 스모크 210건 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a90dd1301e |
데이터소스 배선 전용 편집기 — 조용히 실패하던 문법을 코드가 조립한다
이 시스템의 핵심 자동 채움 경로다. MDataTable 조회 결과의 한 칸을 입력 컨트롤에 꽂는 배선인데,
지금까지 SheetMe 는 ComboBox 에만 안내 없는 자유 텍스트 한 줄이 있었고 나머지 7종에는 항목조차 없었다.
문제는 형식을 어겼을 때의 증상이다. 런타임 파서(ucLoadSheetBase.DataTableBinding_)는 문자열을
쪼개서 읽고, 실패하면 **빈 catch 가 예외를 삼킨다**. 로그도 없다. 디자이너에서는 정상으로 보이고
임상 화면에서만 값이 안 채워지므로 원인 추적이 어렵다. 그래서 문법 골격은 코드가 조립하고
저장 전에 검사한다.
■ 실측이 통설을 뒤집었다
레거시 전용 편집기(fmMDataTableField)는 Select 형 한 가지만 만든다:
{이름}.Select("{필터}")({행}).Item("{필드}") ← 공백 0개
그래서 이것이 '정본'이라고 알려져 있었다.
--db-dtf 진단을 새로 만들어 운영 디자인 1,271건을 센 결과는 반대였다:
Rows 형 1,624건(76%) · Select 형 268건(12%) · 미완성 "MDataTable1.Rows" 234건(11%)
Select 형만 읽었다면 기존 배선의 4분의 3을 편집기가 못 여는 상태로 출시할 뻔했다.
게다가 형태를 함부로 바꾸면 안 된다 — 보조 로더(bzDesignSheetLoader)는 .Select( 분기가 아예 없어
Rows 형만 이해한다. 그래서 **연 값의 형태를 그대로 유지**하고, 새로 만들 때만 Select 형으로 시작한다.
같은 집계에서 운영 결함도 드러났다: 같은 서식에 없는 데이터소스를 가리키는 값 20건
(S0003 은 MDataTable2 를 가리키는데 그 서식에는 MDataTable3 만 있다). 전부 조용히 실패한다.
런타임 조회가 대소문자를 구분하므로 검증에 이름 일치 검사를 넣었다.
■ 레거시보다 나은 점 3가지 (파리티 초과지만 위험 없음)
1. 기존 값을 되읽어 채운다. 레거시는 열 때마다 백지에서 시작해(InitializeSelectedData)
필드 하나만 고치려 해도 전부 다시 골라야 했다.
2. 아무것도 안 고르고 확인하는 경로를 막는다. 레거시는 5조각을 이어붙이므로 리터럴 ")(" 가 저장된다.
3. 저장 전 검사 — 필터·필드의 큰따옴표(파서가 따옴표로 쪼갠다), 이름의 마침표(첫 조각만 떼어 간다),
음수 행. 레거시는 검증이 0이다.
필드 후보를 목록으로 주지는 않았다. 레거시는 쿼리를 실제로 실행해 결과 컬럼을 쓰는데,
그 쿼리에는 테스트 환자 설정이 필요한 치환 토큰이 들어 있어 재현이 별개 작업이다.
대신 필드는 자유 입력으로 두고 그 사정을 화면에 적었다. 실제로 필드에는 SQL 별칭 표현식이
들어오기도 한다(운영 값 EMDOPRDTE||'-'||EMDOPNAME) — 식별자 화이트리스트로 막으면 안 된다.
배선을 붙인 타입은 레거시가 bzMDataTableField 편집기를 다는 8종 그대로다:
TextBox · ComboBox · CheckBox · RadioButton · DateTimePicker · MaskedTextBox · CalcBox · PictureBox.
해석 못 하는 값은 조용히 고쳐 쓰지 않는다 — 인스펙터에 "⚠ 형식을 알 수 없음: 원문" 으로 드러낸다
(운영의 미완성 값 234건이 여기 해당한다).
단위 테스트 18건 추가(표본은 전부 실측·레거시 소스 인용). 편집 스모크 4건 추가.
회귀: 테스트 142/142, 편집 스모크 187건 실패 0,
DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.
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> |
||
|
|
76584e5004 |
레이어 패널을 [200]SheetMe 아웃라인으로 교체 — 페이지 노드·선택 밴드·드래그 재정렬
[200] 의 패널은 코드로 Border 를 쌓는 비가상화 렌더고 SheetMe 는 가상화 ListBox 다. 골격은 SheetMe 것을 지키고 겉모습과 조작 표면만 옮겼다 — 통째로 베끼면 1,067 컨트롤 서식에서 검색어 한 글자마다 수천 비주얼을 다시 만들게 되고, [200] 에 대응물이 없는 컨테이너 자식 재귀 표시가 사라진다. 구조 - 페이지를 별도 섹션이 아니라 아웃라인의 최상위 노드로. 접기·복제·삭제·컨트롤 수·현재 강조 - 맨 아래 '+ 페이지 추가' 행 - 목록 순서를 뒤집었다 — 이제 맨 위가 z-order 최상위(캔버스에서 위에 겹쳐 보이는 것이 목록 위) - 들여쓰기 12 / 32 / +16 - 레이어 패널을 LayerPanelView 로 분리(MainView.xaml 이 1,000줄을 넘겼고, 진단 렌더러가 이 패널만 따로 찍어 라이트·다크·선택 상태를 검사할 수 있어야 한다) 선택 표시 - IsSelected 가 아니라 Highlight 3상태(None/Blue/Navy)가 배경을 그린다. 그룹 전원 선택 시 폴더는 파랑, 멤버는 서브트리 배경이 되고 맞닿는 모서리를 각지게 해 하나의 블록으로 이어진다 - 밴드 위에서는 아이콘·보조 텍스트 색도 함께 뒤집는다. 안 그러면 눈·자물쇠가 1.6:1 로 묻혀 선택한 행에서만 잠금 상태가 안 보이는 상태가 된다 - 신설 토큰 3종(B.LayerSel / B.LayerSubtree / B.OnSelMuted) — [200] 원본값은 각각 흰 글자 3.00:1, 다크 밴드 1.03:1, 라이트 밴드 1.14:1 로 이 저장소가 이미 기각한 값이다 기능 - 드래그로 z-order 재정렬(삽입선 어도너). 임의 index 이동 연산 MoveWithinParent 신설 - 페이지 복제 — 컨트롤 이름을 전부 새로 발급하고 그룹도 복제본끼리 다시 묶는다 ([200] 은 GroupId 를 그대로 둬서 복제 그룹이 원본과 gid 를 공유한다) - 페이지 헤더 클릭 → 캔버스가 그 페이지로 이동(ScrollToPage) - 데이터 배선 배지 — 값이 조회·데이터소스에서 오는 컨트롤 표시 - 검색칩에 테두리(라이트에서 B.Input=B.Panel 이라 경계 대비가 1.00:1 이었다) 이식하지 않은 것: 컨트롤 행 인라인 개명. [200] 이 고치는 값은 표시 전용 별칭이지만 SheetMe 의 컨트롤 이름은 외부 참조 키라 목록에서 무심코 고치면 배선이 끊긴다. 더블클릭은 요청대로 캔버스 이동을 유지하고, 그룹 행에서만 이름 편집으로 간다. 동반 수정: 눈·자물쇠 토글에 되돌리기 지점 추가(400ms 코얼레스), 그룹 이름 비우기 경로가 스냅숏 없이 지워지던 것, 숨김 흐리기를 행 전체가 아니라 이름 줄에만. 편집 스모크의 레이어 검사 19 → 42건. 회귀: 테스트 124/124, 편집 스모크 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a30421f9b5 |
레이어 패널에 [200]SheetMe 그룹 기능 이식 — 폴더 행·계층·타입 필터·다중선택
[200] 의 그룹도 컨테이너가 아니라 GroupId 태그 + 이름 사전이었다. SheetMe 가 이미 같은 구조(ControlElement.GroupId, JsonIgnore)를 갖고 있어 저장 포맷은 그대로 두고 표시·조작 계층만 얹는다 — 왕복 XML 에는 아무 흔적도 남지 않는다. 레이어 목록을 평탄화 결과(LayerRows)로 바꿨다. 그룹 폴더 행과 컨트롤 행이 한 시퀀스로 섞이고, 계층은 들여쓰기 여백으로만 표현한다(TreeView 를 쓰면 가상화와 기존 행 템플릿을 다 버려야 한다 — 인스펙터 섹션이 쓰는 방식과 같게 맞췄다). 새로 되는 것 - 그룹 폴더 행: 접기/펼치기, 멤버 수, 이름 인라인 편집(더블클릭), 그룹 전체 눈·자물쇠 - 그룹 이름 — FormDocument.GroupNames(편집 전용). 비우면 기본 그룹 으로 복귀 - 컨테이너(패널/그룹박스) 자식이 레이어에 보인다. 지금까지는 아예 접근할 수 없었다 - 목록 다중선택(Shift/Ctrl) — 캔버스 선택과 양방향 동기 - 타입 필터: 그 페이지에 실제로 있는 타입만 후보로 띄운다 - 검색 0건·빈 페이지 안내 문구 함께 고친 결함 - 붙여넣기가 원본 그룹에 끼어들었다(Clone 이 GroupId 를 그대로 복사) → 복제본끼리 새 그룹 - 그룹 해제가 선택된 멤버만 풀어 1개짜리 반쪽 그룹이 남았다 → 그룹 전 멤버를 푼다 - 멤버가 1개 이하로 줄어든 그룹이 계속 남았다 → CleanupGroups 자가치유 - 셰브론이 늘 눌린 것처럼 보였다(IsChecked 채움) → MiniChevron 스타일 분리 --edit-smoke 에 레이어 검사 19건 추가(그룹 위생·평탄화·필터·XML 무영향). 회귀: 테스트 124/124, edit-smoke 실패 0, db-smoke 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d09bd90c8e |
레이어 패널: 컨트롤 이름 병기 + 더블클릭으로 캔버스 이동
레이어 목록이 Text 만 보여줘서 chkOrientation1~5 처럼 문구가 같은 컨트롤들이 전부 같은 줄로 보였다. 두 줄로 나눠 아래에 Id 를 병기한다 (Text 가 없어 LayerName 이 곧 Id 인 경우는 중복이라 숨긴다). 그리고 컨트롤이 수백 개인 서식에서는 목록에서 이름을 찾아도 종이 어디에 있는지 알 수 없었다 — 더블클릭하면 해당 페이지로 전환 + 선택 + 캔버스 스크롤. 이미 전부 보이면 스크롤하지 않는다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
394807f8df |
인스펙터 3탭 분할 — 빈 탭은 그리지 않는다
사용자 요청으로 [200] 식 「디자인 / 데이터 / 동작」 3탭을 넣었다. [분류] 속성 49종을 레거시 실사용 근거로 배정했다. 이름만 보고 찍으면 사용자가 속성 하나를 찾아 세 탭을 다 뒤지게 되므로, 각 속성이 레거시에서 실제로 어떻게 쓰이는지를 근거로 삼았다. · GetValue() 가 그 속성을 돌려주면 값이다 — CheckBox/RadioButton 의 Checked 는 IIf(Checked,'Y','') 이므로 초기값(데이터). · DateTimePicker.Format/CustomFormat 은 데이터처럼 보이지만 GetValue 가 Format 과 무관하게 항상 yyyyMMddHHmmss 를 돌려준다 — 그리기 문자열만 바꾸므로 디자인. 확정적. · Mask 는 WinForms 분류로는 Behavior 지만 실제로는 날짜·시간 입력 형식 제약이라 데이터. · Score 는 '점수'라는 이름과 달리 체크됐을 때만 값을 내는 데이터 항목이고 CalcBox.Formula 가 컨트롤명으로 합산한다. · 상용구 2종(RwdRsvWrdYon/RwdOrderAutYon)은 입력 보조처럼 보이지만 조작 훅이다 — 전자는 포커스 핸들러 부착 게이트, 후자는 상용구를 고른 결과로 OCS 처방을 전송하는 게이트. **동작**으로 정정했다 (초안에서는 데이터로 뒀다). 결과: 디자인 21 / 데이터 20 / 동작 8. [빈 탭은 그리지 않는다 — 이번 결정의 핵심] 분류를 마치고 세어 보니 **동작 속성을 가진 타입은 TextBox·Button 둘뿐이고 나머지 14타입(87.5%)에서 동작 탭이 통째로 빈다.** 데이터 탭도 6타입에서 빈다. 빈 탭을 남겨 두면 대부분의 컨트롤에서 '여긴 없습니다'만 보게 되고 사용자는 빈 탭을 계속 눌러보게 된다 — 탭이 없느니만 못하다. 그래서 개수 0인 탭은 알약을 아예 그리지 않고, 남는 탭이 하나뿐이면 알약 줄 자체를 숨긴다. 결과적으로 라벨·선·패널·그룹상자·날짜선택은 탭 없이 평평하게 보이고, TextBox 만 3개가 뜬다. 현재 탭이 새 선택에서 비면 디자인으로 되돌린다 — 디자인은 공통·글꼴이 있어 절대 비지 않는 안전한 착지점이다. [유지] 선택한 탭은 컨트롤을 바꿔도 유지된다. 데이터 탭에서 여러 컨트롤의 태그를 잇달아 손보는 작업이 흔한데 매번 디자인으로 돌아가면 계속 다시 눌러야 한다([200] 은 매번 리셋된다 — 따르지 않았다). [탈출구는 쪼개지 않는다] '전체 속성(고급)'의 레거시 원문 키는 분류 근거가 없고 무엇보다 탈출구다. 탭에 가두면 '고급에도 없네' 상황이 생기므로 모든 탭 하단에 그대로 둔다. [분류표 위치] Core 가 아니라 Designer 계층에 뒀다(PaletteIconCatalog 선례). 탭 구성은 화면 사정이지 서식 데이터의 성질이 아니다. 모르는 키는 편집기 종류로 추론해 사이트 커스텀 속성에 대비한다. 검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 픽셀 차이 0. dialog-shots 진단에 데이터·동작 탭 스냅샷을 추가해 세 탭 전부를 양 테마로 확인. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
abd7eb7d0e |
속성 패널 고도화 — 쌍 배치·스타일 세그먼트·행 축소로 세로 25% 절감
사용자 지적: "속성패널 디자인 보기 불편한데? 고도화해줘".
[실측한 낭비] 단일 선택 시 '공통+글꼴'만으로 354px 를 쓴다. 표준 행 34px 중 실제 필요 높이는
18px(테두리 2 + 글자 16)뿐이라 행마다 12px 가 공기다. 가장 긴 TextBox 는 25행 801px 로 기본 창
뷰포트(약 790px)를 이미 넘겨 스크롤이 강제됐다.
[쌍 배치] X|Y, 너비|높이를 한 줄 2칸으로. 라벨은 칩 바깥 왼쪽 자연폭 — [200] 의 XYPair 규약
(Grid star/10/star)과 같다. 네 줄이 두 줄이 된다.
구조가 핵심이다: **자식 행은 평범한 행 그대로**이고 컨테이너만 새로 만들었다. 값·커밋·"여러 값"
판정이 전부 자식 안에서 끝나므로 커밋 규약(Initialize 는 Commit 을 내지 않고, 값이 바뀔 때만
Undo 스냅샷 1회)이 한 글자도 안 바뀌고, boundsRows 가 자식을 들고 있어 드래그 중 실시간 갱신
(RefreshBoundsRows)도 그대로다. X 만 갈리고 Y 는 같은 다중선택도 칸별로 옳게 표시된다
(그래서 셀마다 전용 Grid 를 둔다 — 오버레이의 포커스 트리거가 AncestorType=Grid 다).
[스타일 세그먼트] 굵게·기울임·밑줄·취소선을 한 줄 아이콘 토글로. 종전에는 굵게·밑줄만 있었고
각각 한 줄이었다. **기울임·취소선은 기술적 제약이 아니라 단순 누락이었다** — LegacyFont 의
파싱·직렬화·렌더·폰트 일괄 변경 대화상자까지 전부 구현돼 있고 인스펙터만 호출을 빠뜨렸다.
CurrentFontOf 가 Italic/Strikeout 을 복사·보존하므로 네 개를 동시에 노출해도 왕복이 안 깨진다.
[행 치수] 인스펙터 전용 지역 스타일로 입력 높이 30→26. 전역 TextBox 를 낮추면 검색칩·대화상자
입력까지 따라 줄어 버튼과 정렬이 깨진다. **ComboBox 는 MinHeight 가 아니라 Height 고정**이라
Height 를 함께 내려야 한다 — 안 그러면 선택 행만 4px 어긋난다(설계 검토에서 잡은 실제 결함).
[라벨] 폭 86→92 + 줄바꿈. 실측상 가장 긴 라벨이 11자("입력 파라미터 컨트롤")라 86px 에서
"액션 대상 컨…" 처럼 잘렸다. 자르면 툴팁을 띄워야 아는 정보가 되므로 접는 쪽을 택했다.
[그 밖에] 태그·마스크 편집기를 여는 "…" 버튼은 26px 칩 옆에서 점 하나로 보여 아이콘 버튼으로.
섹션 헤더 건수를 줄 수가 아니라 **속성 수**로(LeafCount) — 쌍/세그먼트는 한 줄이지만 속성은
여럿이라 그냥 세면 "공통 3"으로 실제보다 적게 나온다.
인스펙터 패널 최소폭 220→250 — 스타일 행이 라벨 92 + 토글 4칸 128 = 220px 를 요구한다.
효과: 공통 5행→3행, 글꼴 4행→3행(속성은 4→6개로 늘었다). TextBox 기준 25행→18행.
'전체 속성 표시' 토글이 기본 창에서 스크롤 없이 보인다(종전에는 한참 아래).
검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0,
종이 렌더 P062 픽셀 차이 0. 인스펙터를 300px 호스트 창에 담아 양 테마로 렌더해 배치 확인
(dialog-shots 진단에 09/10 추가 — 속성 패널 레이아웃 회귀를 눈으로 잡는 자동 경로).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
14944bd316 |
암시 스타일 누락 4종 추가 — ToolTip·ToolBar 자식·포커스링·스크롤 코너
앞 커밋(Window 파생 클래스)과 같은 부류의 결함을 창별 정적 점검이 4건 더 찾아냈다. 전부 "스타일이 없으면 WPF 기본(Aero2) 밝은 크롬으로 떨어지고, 그 위 글자는 앱 전역 암시 TextBlock(B.Ink)을 정상적으로 받아 다크에서 사라진다"는 같은 메커니즘이다. [ToolTip — 48곳] 가장 넓다. MainView 31곳·InspectorView 15곳·대화상자 6곳 등에서 쓰는데 TargetType="ToolTip" 스타일이 저장소 전역에 하나도 없었다. 다크에서 명암비 1.15~1.17:1. B.Surface 채움 + B.Line2 테두리 + 라운드 6 으로 앱 룩에 맞췄다. [ToolBar 자식 키] ToolBar 는 직계 자식에 SetResourceReference 로 자기 키 스타일을 꽂아 암시 Button 스타일을 이긴다 — ToolBar 자체에 스타일을 줘도 그 안 버튼만 Aero2 로 남는다. ButtonStyleKey/ToggleButtonStyleKey/SeparatorStyleKey/TextBoxStyleKey 를 채웠다(미리보기 창). [포커스링] Aero2 기본 검정 점선이 다크 버튼 채움(#262626) 위에서 1.39:1 이고, 커스텀 Button 템플릿에 IsKeyboardFocused 트리거도 없어 Tab 위치를 알 단서가 전혀 없었다. SystemParameters.FocusVisualStyleKey 를 강조색 점선으로 재정의. [ScrollViewer 코너] 가로·세로 막대가 동시에 뜰 때 노출되는 우하단 사각형은 프레임워크 템플릿 소속이라 우리 스타일이 닿지 않는다. 템플릿이 SystemColors.ControlBrushKey 를 DynamicResource 로 읽으므로 그 키를 토큰 사전에서 덮어썼다 — 테마 전환도 따라간다. [진단 보강] --dialog-shots 에 암시 스타일 자가 점검을 추가했다. 툴팁·포커스링·툴바 자식·스크롤 코너는 팝업이거나 프레임워크 템플릿 소속이라 **창을 찍어도 스크린샷에 안 잡힌다** — 값으로 확인하지 않으면 회귀를 놓친다. 떼어 놓은 인스턴스는 트리에 붙기 전이라 Style 이 null 이므로 리소스 사전 존재 여부로 판정한다(처음엔 인스턴스 Style 을 봤다가 오판했다). 검증: 자가 점검 8종 전부 정의됨, 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 픽셀 차이 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
308197c15e |
별도 창 전수 점검 — 암시 Window 스타일이 파생 클래스에 적용된 적이 없었다
사용자 지적: "다른 창으로(새창으로 열리는) 실행하는 항목들도 모두 점검해줘". [근본 원인 — 대화상자 전체가 다크에서 흰 창이었다] WPF 는 암시 스타일을 요소의 **정확한 런타임 타입**으로만 찾는다. 저장소의 모든 창이 Window 를 상속한 클래스(SheetOpenDialogView : Window, QueryEditorWindow : Window …)라 DesignerTheme.xaml 의 <Style TargetType="Window"> 는 **단 한 번도 적용된 적이 없다**. 결과: 창 배경이 WPF 기본 흰색으로 남고, 그 위 글자는 암시 TextBlock 스타일(B.Ink #EDEDED)을 정상적으로 받는다 → **다크 테마에서 흰 바탕에 흰 글자**. 쿼리 편집기의 변수 목록처럼 라벨이 통째로 사라지는 화면이 나왔다. 라이트에서 눈에 덜 띈 이유는 우연이다 — 기본 흰색(#FFFFFF)이 의도값 B.AppBg(#F5F5F5)와 거의 같아서다. 그래서 지금까지 라이트만 보고는 발견되지 않았다. 수정: 스타일에 x:Key="ThemedWindow" 를 주고 창 10종에 직접 걸었다(코드로 짓는 ColorPickerWindow 는 생성자에서 TryFindResource). 순수 Window 인스턴스용 암시 스타일은 BasedOn 으로 남겼다. 앞으로 창을 추가할 때 잊지 않도록 스타일 위에 경고 주석을 붙였다. [신규 진단 --dialog-shots <출력폴더>] 이 결함은 정적 분석으로는 못 잡았다(앞선 감사에서 "대화상자들은 암시 Window 스타일을 그대로 받는다"고 잘못 결론냈다). 실입력 주입은 화면 잠금·세션 격리 상태에서 OS 가 거부한다 (SetCursorPos 무시, SendKeys "Access is denied" — 이번에도 중간부터 막혔다). 그래서 창을 화면 밖(-10000)에 띄워 RenderTargetBitmap 으로 찍는 진단을 만들었다. 창 8종 × 2테마 = 16장을 한 번에 남기고, 각 창의 **해석된 Background 와 Style 적용 여부를 텍스트로 함께 보고**한다 — 픽셀만 보면 원인을 못 가린다(실제로 이 한 줄이 원인을 확정했다). 표본 데이터는 실제 사용 시와 같은 형태로 넣었다(빈 껍데기를 찍으면 의미가 없다). 함정 2개를 코드에 남겼다. · 기본 ShutdownMode 가 OnLastWindowClose 라 찍고 닫는 순간 앱이 종료된다 → OnExplicitShutdown. · Window.Content 만 렌더하면 창 배경이 빠져 투명(=PNG 검정)이 되고, 다크처럼 보이는 착시로 라이트 결함이 가려진다 → 배경을 먼저 칠하고 그 위에 콘텐츠를 그린다. [함께 고친 것 — 여러 줄 입력이 세로 가운데 정렬] 쿼리 편집기의 SQL 이 큰 상자 한가운데 떠 있었다. 템플릿 트리거가 ScrollViewer 의 VerticalContentAlignment 만 Stretch 로 바꿨는데, 텍스트를 배치하는 건 TextBox 자신의 VerticalContentAlignment 이라 아무 효과가 없었다. Style.Triggers 로 옮겨 Top 으로 두고 여러 줄일 때 Padding 도 넉넉히 준다. 인스펙터의 여러 줄 속성 행에도 함께 적용된다. 검증: 창 8종 × 2테마 전부 재렌더해 배경 확인(다크 #0E0E0E / 라이트 #F5F5F5, 미리보기만 의도대로 B.CanvasBg). 테스트 124/124, edit-smoke 실패 0, **종이 렌더 P062 픽셀 대조 차이 0**. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |