feat: advance legacy UI behavior parity

This commit is contained in:
2026-07-18 10:18:29 +09:00
parent d7ec571343
commit 1302b1b92f
71 changed files with 11244 additions and 1153 deletions

View File

@@ -233,7 +233,9 @@
- Busy/고정 섹션 배치 중에는 action뿐 아니라 로컬 disclosure 변경도 차단한다.
- MA5/MA20은 행 추가 시 저장하지 않고 s8010 PREPARE, F8, NEXT, 자동 갱신의 장면 생성 시점마다
현재 전역 체크 상태를 다시 읽는다. named playlist identity에도 MA 플래그를 저장하지 않는다.
- 원본의 `uc_3 == null` 부모 더블클릭 예외와 부분 추가 버그는 재현하지 않는 명시적 결함 제외다.
- 공용 UC1의 부모 일괄 추가 중 부분 mutation은 재현하지 않는다. 다만 업종 탭의 부모는 실제로
22개 루프 전에 `uc_3 == null` 예외가 발생해 아무 행도 추가하지 않으므로, 이 항목은 Slice 12의
재감사 결과에 따라 실제 관찰 동작인 no-op으로 고정했다.
상세 근거와 결함 제외는 `docs/UC1_TREE_EVENT_PARITY.md`에 기록했다.
@@ -329,11 +331,198 @@
비활성화하고 API 실패나 0 이하는 축별 4 DIP로 fail-safe한다. 실제 drop 내용은 payload가
아니라 C#의 현재 선택 집합과 physical 순서가 계속 권위본이다.
## Slice 11: UC3 종목 비교 중복행과 전체 삭제
상태: 코드 및 자동 테스트 완료, 실제 파일·DB·Tornado 명령은 실행하지 않음.
원본 근거:
- `Control/UC3.cs:100-145` 비교쌍 추가와 `File_Save`
- `Control/UC3.cs:147-154` 선택 행 삭제
- `Control/UC3.cs:156-168` 전체 삭제 Yes/No
- `Control/UC3.cs:171-183` Up/Down 저장
고정한 계약:
- 원본 `btnAdd_Click`에는 중복 검사가 없으므로 같은 두 대상을 같은 순서로 여러 번 추가할 수
있다. 각 행은 별도 stable row ID를 가지며, 이동·선택 삭제는 그중 활성 행 하나에만 적용한다.
- 화면의 비교 조작 표시는 원본 Designer와 같은 `◀▶`, `추가`, `UP`, `DOWN`, `DEL`,
`DEL ALL`을 사용하고, 원본에 없던 별도 초기화 버튼은 표시하지 않는다.
- CP949 parser/import도 같은 비교쌍의 반복을 허용하되 물리 순서, 연속 RowNumber, 최대 행 수와
형식·인코딩 검증은 유지한다. 저장 후 재로드해도 반복 행과 순서가 보존된다.
- `DEL ALL`은 Web의 `window.confirm`이나 직접 파일 변경으로 처리하지 않는다. C#이 정확한
`MmoneyCoder` caption과 `모두 삭제하겠습니까?` 문구의 Yes/No 상태를 소유하며, No는
0회, Yes는 confirmation을 먼저 소비한 뒤 정확히 1회만 mutation한다.
- confirmation 동안에는 화면 재연결용 ready와 confirm/cancel 이외의 bridge intent를 모두
무시한다. 이는 원본의 modal `MessageBox`가 MainForm과 다른 child action을 막던 동작을
native 경계에서도 보장한다.
- Yes/No bridge payload는 빈 객체만 허용하며 row ID, 종목 identity 또는 action kind를 받지
않는다. 삭제 대상 목록의 revision이 바뀌면 저장하지 않는다.
재현하지 않은 원본 버그:
- 원본 `File_Save`는 RowCount가 0이면 파일을 쓰지 않아 마지막/전체 삭제가 재시작 뒤 되살아난다.
새 구현은 빈 CP949 파일을 원자적으로 1회 저장해 삭제를 영속화한다. 이는 기존 one-time import와
재시작 영속성 계약을 깨지 않기 위한 명시적 데이터 안전 교정이다.
## Slice 12: UC2 업종 목록·비교 입력과 업종 UC1 단일 루트
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado/UI 명령은 실행하지 않음.
원본 근거:
- `Control/UC2.cs:32-76` 목록 조회, 즉시 선택, double-click 회전과 텍스트 swap
- `Control/UC2.Designer.cs:43-106` 목록 위/비교 입력 아래 배치와 편집 가능한 txt1/txt2
- `Control/UC1.cs:154-180`, `204-220`, `330-351` 업종 루트·리프와 1열/2열 조건
- `MainForm.cs:2966-2985` 업종 UC1을 `uc_3 == null`로 생성하는 실제 경로
고정한 계약:
- 업종 액션 트리는 원본 INI처럼 `[코스피]` 또는 `[코스닥]` 루트 하나와 원본 순서의 자식
22개만 표시한다. 기존 Web의 인위적인 5개 그룹은 제거했다.
- 업종 루트 double-click은 공용 UC1이 22개 루프 전에 null인 `uc_3`를 읽고 바깥 catch가
예외를 삼키므로 실제 바이너리처럼 조용한 no-op이다. 자식 double-click만 한 컷을 추가한다.
- 목록 single-click은 지연 없이 C# 활성행을 바꾸고, state 회신은 기존 DOM을 교체하지 않아
같은 행의 두 번째 click과 double-click을 잃지 않는다.
- 목록 전체가 하나의 tab stop이며 ArrowUp/Down, Home/End가 원본 ListBox처럼 활성행을 즉시
바꾼다. 행 버튼은 개별 Tab 순서에 들어가지 않는다.
- 화면 배치는 목록, `업종비교`, txt1/교환/txt2 순서이고 교환 버튼은 원본 `◀▶` 표기다.
- txt1/txt2는 편집 가능하다. Web은 slot/value만 닫힌 intent로 보내며 C#의 `FirstText`
`SecondText`가 권위 상태를 가진다. action intent에는 입력 문자열을 섞지 않는다.
- 목록 double-click은 수동 txt1을 txt2로 민 뒤 새 업종명을 txt1에 넣고, 교환은 한 칸만
채워진 경우까지 두 문자열을 그대로 바꾼다. 1열판은 계속 활성 목록 행을 사용하고 2열판만
두 편집 문자열을 사용한다.
- 입력 state 회신은 focused input을 덮거나 workspace를 교체하지 않아 blur 직후 swap이나
자식 double-click이 사라지지 않는다.
- 원본 쿼리가 돌려준 중복 이름·코드 행은 500행 안전 상한 안에서 순서 그대로 표시하고 클릭한
인덱스로 구분한다. 장면 데이터 조회의 exact-one-row 검증은 유지해 모호한 결과는 PREPARE 전에
fail closed한다.
명시적 안전 교정:
- 원본 TextBox 기본 최대 길이 대신 실제 scene selector 경계인 128자로 제한하고 control,
surrogate, U+FFFD를 거부한다. 편집 중 빈 값은 허용하되 2열판 생성 때 공백뿐인 값과 subject
구분자인 `-`가 든 값은 거부한다. SQL은 계속 parameterized equality로만 실행된다.
## Slice 13: UC4 테마 검색·세션·미리보기
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado/UI 명령은 실행하지 않음.
원본 근거:
- `Control/UC4.cs:109-247`, `429-439` 테마 검색, 선택, 현재가·예상체결가와 6·12종목 버튼
- `Control/UC4.Designer.cs` 검색 → 결과 → 정렬 → 액션 → 종목 미리보기의 세로 배치
- `Control/UC4.cs` NXT 조회의 현재 시각 기준 오전/오후 세션 선택
고정한 계약:
- UC4 검색 intent는 검색어만 받는다. 원본 화면에 없던 NXT 세션 선택기는 제거했고, C#이 로컬
시각 0~12시에는 장전, 13~23시에는 장후 세션을 탭 진입과 검색 시점마다 결정한다.
- 화면은 검색 입력, 테마 결과, 정렬 라디오, `6종목`, `12종목`, `현재가`, `예상체결가`, 종목
미리보기 순서의 한 열 구조다. 결과·미리보기의 인위적인 좌우 2열 구조는 제거했다.
- 검색 결과는 원본처럼 KRX 결과 다음 NXT 결과이며 각 DB 결과 순서를 그대로 유지한다. Web은
결과 ID와 action ID만 전송하고 테마 코드·종목 코드·세션을 만들지 않는다.
- 결과 클릭은 C# receipt를 기다리지 않고 즉시 선택 표시를 바꾸되 opaque ID를 정확히 한 번만
보낸다. 동일 구조의 receipt는 결과 DOM을 교체하지 않아 연속 클릭과 focus를 잃지 않는다.
- NXT 테마의 예상체결가 버튼은 Web에서 임의로 비활성화하지 않는다. 클릭하면 원본과 같은
`NXT는 데이터가 없습니다.` 안내를 native 경계에서 표시하고 플레이리스트 mutation은 0회다.
- 플레이리스트에는 원본의 표시 열과 내부 scene identity를 분리해 저장하고, 미리보기가 0행인
선택으로는 빈 컷을 만들지 않는다.
명시적 안전 교정:
- DB 검색·미리보기 결과는 generation과 opaque selection ID로 연결한다. 늦게 끝난 이전 검색이
새 결과나 선택을 덮지 못하며, 동일 이름이 모호하거나 미리보기가 비어 있으면 fail closed한다.
## Slice 14: UC5 해외지수·해외업종·해외종목
상태: 코드 및 자동 테스트 완료, Debug/Release x64와 unsigned MSIX 생성 완료. 실제 DB/Tornado
명령은 실행하지 않음.
원본 근거:
- `Control/UC5.cs:33-47`, `50-137`, `141-243` 초기 목록, 검색, 선택과 컷 추가 순서
- `Control/UC5.Designer.cs` 해외지수 → 해외 업종지수 → 해외 종목의 세로 배치와 액션 버튼
- `Data/Request/봉차트/CandleforeignStock.cs` 해외종목 봉차트의 이름·심볼·국가 조건
- `Data/Request/판데이터기본/종목/STOCK_FORGIEN_PAN.cs` 해외종목 1열판의 정확한 종목 identity
고정한 계약:
- 화면은 원본과 같은 세로 3그룹이며, 별도 action tree·상세 패널·선택 개수·국가 코드는
표시하지 않는다. 검색 Enter, 즉시 선택, 한 번의 Tab 진입과 ArrowUp/Down/Home/End 선택을
지원한다.
- 빈 검색은 원본처럼 미국·대만 초기 목록만 조회하고, 검색어가 있으면 `F_FDTC='1'`인 모든
해외 국가를 이름으로 조회한다. DB 순서와 같은 이름의 서로 다른 종목을 그대로 보존한다.
- 원본 FarPoint `SheetView``ActiveRowIndex=-1`, `RowCount=0`에서 첫 `AddRows` 직후 활성행을
0으로 바꾼다. 고정 지수와 검색 결과의 첫 행을 같은 방식으로 자동 활성화해 별도 선행 클릭
없이 원본처럼 액션 버튼을 사용할 수 있다.
- 원본에는 결과 상한이 없으므로 기존 100건 기본 상한을 데이터 경계의 안전 상한인 500건으로
넓혔다. 500건을 넘으면 조용히 숨기지 않고 목록 제목에 `검색 결과가 많아 500건까지만 표시합니다.`
표시한다.
- Web에는 generation에 묶인 opaque selection ID와 표시명만 전달한다. 심볼·국가·data code는
C# 권위 상태에만 보관하며 action intent에는 포함하지 않는다.
- 플레이리스트 표시 열은 원본처럼 `해외지수/해외업종/해외종목`, 종목명,
`1열판기본` 또는 `5/20/60/120/240일`, `1/1`만 사용한다.
- 해외종목은 내부 `symbol|nation|1` identity로 5001 봉차트와 8010 1열판에 전달해 같은
표시명도 정확한 심볼·국가로 조회한다. 기존 이름만 저장된 레거시 행은 미국·대만 name-only
조회와 계속 호환한다.
- 현재 K3D 장면 경계가 처리하는 두 글자 국가 코드는 전 국가에 대해 실행 가능하다. 세 글자
국가 코드 결과는 목록에는 보존하되 컷 생성 단계에서 명시적으로 fail closed한다.
- 동일 구조의 native 회신은 목록 DOM과 focus를 교체하지 않으며, 늦게 끝난 검색은 새로운
generation의 결과나 선택을 덮지 못한다.
명시적 안전 교정:
- 원본의 이름 단독 장면 조회가 같은 이름의 여러 국가·심볼을 혼동할 수 있어, 신규 행은 정확한
내부 identity를 필수로 사용한다. 이 identity는 WebView 경계를 통과하지 않는다.
- 지원하지 않는 국가 코드, 모호한 name-only 결과, 빈 선택은 PREPARE 전에 차단한다.
## Slice 15: Release 패키지 컨텍스트와 실제 개발 환경 사전 검증
상태: Release x64 개발 등록 패키지의 UI-only/DryRun 검증, 실제 개발 DB SELECT-only 검증,
원본 Cuts 범위 검사와 K3D/Tornado 읽기 전용 사전 검사를 완료했다. COM/KTAP 송출 명령과 DB write는
실행하지 않았으며 검증 뒤 `MBN_STOCK_WEBVIEW` 프로세스는 남아 있지 않다.
검증 결과:
- 패키지는 정확한 `Wickedness.MBNStockWebView.LegacyParity_0.1.0.0_x64__qbv3jkvsn3aj0` Release x64
개발 등록에서 실행했다. URL은 `https://legacy-parity.mbn.local/index.html`, 상태는 `DRY RUN`/`IDLE`였고,
guard 이후 native postMessage, mouse/keyboard 입력, DB read/write intent, 단일 인스턴스 재활성화와
Tornado 포트 연결은 모두 0건이었다. 중립 종료 대화상자의 기본 종료 동작을 정확히 1회 사용해 정상 종료했다.
- 패키지 증거 JSON은
`C:\Users\MD\source\repos\MBN_STOCK_WEBVIEW\artifacts\package-ui-only-smoke\legacy-package-ui-only-20260718T011058894Z.json`
(SHA-256 `2AD2C06EB14DB9684548A5D086B9FA4EC53E504D6722400E1F16B6E600BCD341`)이고,
화면 캡처는 같은 폴더의 `legacy-package-ui-only-20260718T011058894Z.png`
(SHA-256 `1CC02BEE872A15E64D4A74F8E489A212B4547464FB27784C4975E523B5132806`)이다.
- 생성된 MSIX는
`C:\Users\MD\source\repos\MBN_STOCK_WEBVIEW\src\MBN_STOCK_WEBVIEW.LegacyParityApp\AppPackages\MBN_STOCK_WEBVIEW.LegacyParityApp_0.1.0.0_x64_Test\MBN_STOCK_WEBVIEW.LegacyParityApp_0.1.0.0_x64.msix`
(SHA-256 `3F68625C0D2ACBBFD673F2F2869944C5818A8732E94A91BA7E5C7F2707458228`)이며,
금지된 외부 자산·비밀 값은 포함하지 않았다.
- 실제 개발 Oracle/MariaDB에 SELECT-only smoke를 실행해 Oracle 21 연결, MariaDB 연결, 종목·해외 선택기,
전문가·거래정지·PList 조회를 확인했다. 필수 scene data 33/33, 전체 query 58개가 통과했고 DB write는 0건이다.
현재 NXT theme preview 48건이 빈 결과인 것은 실제 데이터 상태이며 코드 실패로 판정하지 않는다.
- 원본 읽기 전용 Cuts의 reachable builder 34개와 active alias 45개를 검사했다. `.t2s` 누락·unsafe path·root escape는
모두 0건이다. 의존 자산은 23개 중 14개가 없으며, `s5006 Video\큐브배경.vrv` 1개와
`s6001 Video\20201008_<13 countries>.vrv` 13개 패턴이다. 따라서 5001은 실제 검증 후보로 유지하고
5006/6001은 자산 제공 전까지 차단한다.
- K3D 등록은 HKLM Registry64, native AMD64 type library와 KAEngine/KAEventHandler 상호 매핑이 모두 유효했다.
현재 `Tornado2 v2.7.5.49 x64 DSX10.0 S.R2`의 PID 41308, 제목 `PGM`, listener
`0.0.0.0:30001`을 읽기 전용으로 확인했다. COM activation, CONNECT, PREPARE, TAKE IN, NEXT,
TAKE OUT과 DISCONNECT는 실행하지 않았다.
남은 실제 검증:
- 현재 PGM의 5001 page 1 검증은 이전 승인을 재사용하지 않고 새 회차·계획 해시로 승인받는다.
먼저 CONNECT/PREPARE의 known outcome과 Network Monitoring 기록을 확인하고, 그 뒤 TAKE IN/PGM 화면/
TAKE OUT은 허용 횟수를 명시한 별도 승인 범위에서만 실행한다. 결과가 불명확하면 재시도하지 않는다.
- Network Monitoring 기록과 PGM 출력은 사용자가 함께 확인한 증거를 남긴다.
- 5006/6001은 누락된 외부 동영상 자산이 안전하게 제공되고 coverage가 다시 통과하기 전에는 실제 검증하지 않는다.
## 다음 대조 순서
1.package에서 실제 마우스·키보드·컷/playlist drag 회귀
2. ThemeA/EList 편집 save/delete/close의 세부 확인 문구와 focus 순서 대조
3. 자동 갱신 UI의 실제 실행 상태 기반 통합 테스트와 종료 상태 행렬
4. 다음 Designer handler 묶음의 원본 코드·상태 전이 대조
1.회차 승인으로 현재 PGM 대상 5001 page 1 CONNECT/PREPARE known outcome 검증
2. Network Monitoring과 PGM 출력 확인 뒤 별도 승인 범위의 TAKE IN/TAKE OUT 검증
3. s5006/s6001 외부 동영상 자산 보완과 Cuts coverage 재검증
4. 전체 근거 검토 뒤 변경 사항 커밋·원격 반영
각 항목은 실제 원본 코드 근거와 회귀 테스트가 모두 생기기 전에는 완료로 판정하지 않는다.