176 lines
13 KiB
Markdown
176 lines
13 KiB
Markdown
# 운영자 UI 동등성 인벤토리
|
|
|
|
기준일: 2026-07-15
|
|
|
|
## 제품과 판정 기준
|
|
|
|
현재 이관 앱은 웹사이트가 아니라 `.NET 8 / C# / WinUI 3 / WebView2` Windows 데스크톱
|
|
프로그램인 `MBN_STOCK_WEBVIEW.LegacyParityApp`이다. WebView2는 패키지 안의 로컬
|
|
HTML/CSS/JavaScript를 표시한다.
|
|
|
|
Web은 원본 UI를 표현하고 사용자 intent를 전달한다. 원본 이벤트의 상태 전이, DB identity,
|
|
playlist, PageN과 송출 상태는 `LegacyOperatorController`의 C# snapshot이 권위다. Web 메시지는
|
|
`LegacyBridgeProtocol`의 strict JSON allowlist에 없는 필드·값·명령을 거부한다.
|
|
|
|
이 문서의 판정은 다음을 구분한다.
|
|
|
|
| 판정 | 의미 |
|
|
|---|---|
|
|
| UI/이벤트 이관 | 원본 컨트롤과 클릭 이벤트가 실제 LegacyParityApp 화면 → strict bridge → C# workflow로 연결됨 |
|
|
| 자동 동등성 | 원본 inventory·DTO·mutation·호출 순서·경계값을 자동 테스트로 비교함 |
|
|
| 개발 DB 통합 | 실제 개발 Oracle/MariaDB에서 create/readback/delete/absence를 확인함 |
|
|
| 개발 PGM 통합 | 개발 Tornado2 Network Monitoring과 PGM 화면을 함께 확인함 |
|
|
| 외부자산 필요 | 코드 경로는 있으나 Git/MSIX 밖의 실제 영상·배경이 제공되지 않음 |
|
|
|
|
## 원본 보존
|
|
|
|
- 원본 `C:\Users\MD\source\repos\MBN_STOCK_N`은 읽기 전용으로만 분석했다.
|
|
- 원본 `Cuts`도 읽기 전용 검증에만 사용하며 수정·복사하지 않는다.
|
|
- 비밀번호, 라이선스, 벤더 DLL, 인증서, 실제 컷·영상·배경과 로컬 설정은 Git에 넣지 않는다.
|
|
|
|
## 원본 실행 action inventory
|
|
|
|
동적 테마·전문가 DB 행과 전용 편집기의 데이터 행은 아래 고정 수량에 중복해 더하지 않는다.
|
|
|
|
| 집합 | 원본 UI | 수량 | 현재 경계 | 상태 |
|
|
|---|---|---:|---|---|
|
|
| fixed action | MainForm 해외·환율·지수와 UC1 | **328** | `LegacyFixedActionCatalog` + strict action/section intent | 이관 |
|
|
| 업종 action | UC1 + UC2 | **44** | `LegacyIndustryActionCatalog` + C# section batch | 이관 |
|
|
| 종목 컷 | MainForm 좌측 검색·컷 | **31** | `LegacyStockCutCatalog` | 이관 |
|
|
| 비교 action | UC1 + UC3 | **9** | 24개 target + `LegacyComparisonWorkflowController` | 이관 |
|
|
| 업무 탭 | MainForm TabControl | **10** | identity 기반 활성화·순서 변경 | 이관 |
|
|
|
|
328개 fixed action 중 수동 전제 6개는 임의 DTO로 만들지 않는다. 개인·외국인·기관
|
|
순매도 3개는 FSell의 신뢰된 5행 입력으로, 5단·6종목·12종목 트리의 VI placeholder 3개는
|
|
원본 VIList가 만든 `s5074/5074` 5개 단위 PageN으로만 활성화한다.
|
|
|
|
## 화면 단위 현황
|
|
|
|
| 원본 화면 | 이관된 핵심 동작 | 현재 구현 | 개발 DB/파일 검증 | 개발 PGM/자산 경계 |
|
|
|---|---|---|---|---|
|
|
| MainForm | 종목 검색, 31컷, playlist, 배경, 송출, 단축키, 종료 확인 | LegacyParityApp shell + C# controller | 실제 DB 검색/read smoke PASS | 송출 최종 결과는 검증 문서에 기록 |
|
|
| UC1 | 328 fixed, 업종·비교 트리, 부모/leaf batch | C# 폐쇄 inventory와 원자적 section materialize | read-only selector 검증 | 장면별 PGM은 별도 |
|
|
| UC2 | 업종 검색, 두 슬롯 회전·교환, 44 action | industry workflow/controller | Oracle read PASS | 장면별 PGM은 별도 |
|
|
| UC3 | 비교 대상·검색·쌍 CRUD·9 action | comparison workflow/controller | 원본 8쌍 1회 import·재시작 영속성 검증 | 장면별 PGM은 별도 |
|
|
| UC4 | KRX/NXT 테마 검색·preview·5/6/12 선택 | theme workflow; 목록 행 단일 클릭 | KRX/NXT selector PASS | 외부 scene 자산과 PGM 별도 |
|
|
| UC5 | 해외업종·해외종목·해외지수 | overseas workflow/controller | Oracle selector PASS | `s6001` 영상 13개 외부자산 미제공 |
|
|
| UC6 | 전문가 검색·추천 preview·5행 선택 | expert workflow; 목록 행 단일 클릭 | selector PASS | 장면별 PGM은 별도 |
|
|
| UC7 | 거래정지 검색·현재/120일 캔들 | trading-halt workflow; 원본 단일 클릭 | Oracle selector PASS | 장면별 PGM은 별도 |
|
|
| GraphE | 4개 재무 CRUD, 정렬·찾기, 행 더블클릭 playlist 추가 | `LegacyManualFinancialWorkflow` | 개발 DB 4개 테이블 write PASS | 전용 scene PGM 별도 |
|
|
| FSell | 개인·외국인·기관 각 5행, 확인 시 저장+추가+닫기 | `LegacyManualListsWorkflow` + trusted store | 원본 형식 read와 격리 write 검증 | `s5025` trusted 파일 전제 |
|
|
| VIList | 종목 추가·삭제·이동, 5개 단위 page, 확인 시 저장+추가+닫기 | `LegacyManualListsWorkflow` + trusted store | 원본 형식 read와 격리 write 검증 | `s5074` 전용 입력 PGM 별도 |
|
|
| PList | 목록, 행 더블클릭 load/save, 확인, 삭제 | `LegacyNamedPlaylistWorkflow` | 개발 DB create/save/read/delete/absence PASS | 복원 항목별 scene PGM 별도 |
|
|
| AList | 다음 코드와 프로그램명 신규 생성 | named playlist workflow에 통합 | 위 named playlist 시나리오로 PASS | 해당 없음 |
|
|
| ThemeA | KRX/NXT 테마 생성·편집·삭제, 종목 구성·순서 | `LegacyOperatorCatalogWorkflow` | 개발 DB KRX/NXT 각 1개 PASS | scene PGM 별도 |
|
|
| EList | 전문가 생성·편집·삭제, 추천 종목·금액·순서 | `LegacyOperatorCatalogWorkflow` | 개발 DB expert 1개 PASS | scene PGM 별도 |
|
|
|
|
UC4/UC6의 결과 행은 고정 action 트리의 click handler에 버블링하지 않는다. UC4 테마,
|
|
UC6 전문가와 UC7 거래정지는 원본처럼 **단일 클릭**으로 선택/playlist 동작을 수행하고,
|
|
fixed/industry/comparison 트리의 원본 double-click과 독립적으로 처리한다.
|
|
|
|
## MainForm 이벤트 매핑
|
|
|
|
| 원본 이벤트 | 원본 의미 | LegacyParityApp 동작 |
|
|
|---|---|---|
|
|
| `Form1_Load` | 자산·DB·탭·Tornado 초기화 | WebView local UI와 C# composition root 초기화; 기본 `DryRun` |
|
|
| `tabControl1_SelectedIndexChanged` | 탭별 최초 데이터 조회 | 10개 identity별 C# activation gate; 최초 진입 1회 load, 재진입 snapshot 유지 |
|
|
| 탭 drag 이벤트 | 10개 탭 표시 순서 교환 | identity 기반 drag/키보드 이동; active tab·playlist 불변 |
|
|
| 검색 버튼/Enter | 종목 검색 | 원본 KOSPI→KOSDAQ→NXT 순서와 마지막 이름 일치 규칙 유지 |
|
|
| 컷 click/double-click/drag | 원본 31컷 선택·추가 | plain/Ctrl/Shift/Ctrl+Shift와 원본 double-click 판정, batch 추가 |
|
|
| playlist click/key/buttons | 선택, enabled, 이동, 삭제 | C# playlist authority; PREPARED/PROGRAM/pending/unknown에서 mutation 차단 |
|
|
| F2/F3 | 배경 선택·없음 | trusted sibling background root만 허용; 누락·reparse·escape fail closed |
|
|
| F8 | TAKE IN | idle이면 내부 PREPARE → TAKE IN을 한 번에 실행 |
|
|
| NEXT | Page NEXT 또는 다음 행 | 같은 scene PageN 여부를 C#이 판정 |
|
|
| Escape | TAKE OUT | modal이 없을 때만 StopAll 기반 TAKE OUT intent |
|
|
| FormClosing | 종료 확인 | 기본 취소 확인창; 자동 TAKE OUT/반대 명령 없음 |
|
|
|
|
## FarPoint 대체와 클릭 규칙
|
|
|
|
FarPoint UI는 Web table/list로 표현한다. 다음 의미는 원본과 같이 유지한다.
|
|
|
|
- plain/Ctrl/Shift/Ctrl+Shift 선택과 현재 행
|
|
- column header 정렬과 현재 표시 순서
|
|
- 위/아래 이동, 선택/전체 삭제와 enabled 토글
|
|
- single-click과 double-click을 컨트롤별로 구분
|
|
- 부모 section 전체 추가는 모든 자식을 먼저 materialize한 뒤 한 번에 commit
|
|
- 수동/지원 불가 child가 있으면 부분 추가 없이 전체 차단
|
|
- modal이 열리면 F2/F3/F8/Escape, Delete/Space/Home/End 등 전역 동작 격리
|
|
- stale generation, 예상하지 않은 row id, 추가 payload field를 strict bridge에서 거부
|
|
|
|
GraphE 행 double-click은 fresh DB record를 다시 확인한 뒤 한 행을 추가하고 닫는다.
|
|
Theme 결과 double-click은 원본의 5행 현재가 경로를 사용한다. PList 행 double-click은
|
|
현재 save/load 모드에 맞춰 동작하며, 이름만으로 DB identity를 추정하지 않는다.
|
|
|
|
## 원클릭 TAKE IN과 숨김 PREPARE
|
|
|
|
원본 운영 화면의 핵심 동작은 별도 PREPARE를 먼저 누르는 것이 아니라 F8/TAKE IN 한 번으로
|
|
송출하는 것이다. 현재 보이는 TAKE IN은 idle에서도 활성 조건을 만족하면 다음을 실행한다.
|
|
|
|
1. playlist selection과 page를 C# snapshot으로 고정
|
|
2. DB/신뢰 파일 DTO와 PageN을 fresh load
|
|
3. 장면 mutation과 transaction 순서를 적용해 `Prepare` 정확히 1회
|
|
4. 성공한 prepared scene에 `TakeIn` 정확히 1회
|
|
|
|
엔지니어링용 native PREPARE control과 explicit workflow는 테스트를 위해 유지하지만 Web UI에서
|
|
`hidden`이다. TAKE IN 실패·timeout·`OutcomeUnknown`이면 자동 재시도하지 않으며 성공한
|
|
prepared ownership을 추측해 지우지 않는다.
|
|
|
|
## PageN과 NEXT
|
|
|
|
| scene | page size | 규칙 |
|
|
|---|---:|---|
|
|
| `s5074` | 5 | 테마/전문가/VI 등 5행 경로 |
|
|
| `s5077` | 6 | 6종목 테마 경로 |
|
|
| `s5088` | 12 | 12종목 테마 경로 |
|
|
|
|
`pageCount = min(20, ceil(itemCount / pageSize))`를 사용한다. 0행, 정확한 경계,
|
|
마지막 부분 페이지, 20페이지 cap, 21페이지 요청 거부, 같은 scene의 `GetPlayingScene`
|
|
Page NEXT와 다음 enabled playlist 항목 NEXT를 자동 테스트로 비교한다.
|
|
|
|
## 개발 DB write 증적
|
|
|
|
실제 개발 Oracle/MariaDB에서 production executor를 사용한 9개 충돌 격리 시나리오가
|
|
create/save → fresh readback → delete → absence를 통과했다.
|
|
|
|
| 기능 | 시나리오 |
|
|
|---|---:|
|
|
| GraphE `INPUT_PIE/GROW/SELL/PROFIT` | 4 |
|
|
| PList/AList named playlist | 2 |
|
|
| ThemeA KRX/NXT | 2 |
|
|
| EList expert | 1 |
|
|
| 합계 | **9** |
|
|
|
|
이는 개발 DB 통합 증거이며 운영 DB write를 수행했다는 뜻이 아니다. write 재시도는 없고,
|
|
결과가 불명확하면 cleanup과 다음 시나리오도 실행하지 않는 fail-closed 규칙을 유지한다.
|
|
|
|
## read-only·scene·asset 증적
|
|
|
|
- 실제 Oracle/MariaDB 필수 scene selector **33/33**, 전체 query 58건 PASS
|
|
- 원본 scene builder 자동 기준선 **35/35**
|
|
- MainForm 도달 runtime 34개 builder와 active alias **45개**
|
|
- 승인된 Cuts에 45 alias 대응 `.t2s` 존재
|
|
- `s5006` `Video\큐브배경.vrv` 1개와 `s6001` 국가 영상 13개는 외부자산 미제공
|
|
- sibling `배경\기본.vrv`도 외부자산 미제공
|
|
|
|
마지막 14개 영상과 `기본.vrv`의 부재는 UI/scene 코드 실패가 아니다. 해당 기능은 자산이
|
|
제공될 때까지 COM 전에 차단하며 임의 파일로 대체하지 않는다.
|
|
|
|
## 구현·테스트 빠른 링크
|
|
|
|
| 영역 | 현재 실행 구현 | 주요 테스트/근거 |
|
|
|---|---|---|
|
|
| WinUI 3 + WebView2 shell | [MainWindow.xaml.cs](../src/MBN_STOCK_WEBVIEW.LegacyParityApp/MainWindow.xaml.cs), [Web app](../src/MBN_STOCK_WEBVIEW.LegacyParityApp/Web/app.js), [Web layout](../src/MBN_STOCK_WEBVIEW.LegacyParityApp/Web/index.html) | [thin client](../tests/LegacyParityWeb/thin-client.test.cjs) |
|
|
| C# 업무 권위 | [LegacyOperatorController.cs](../src/MBN_STOCK_WEBVIEW.LegacyApplication/LegacyOperatorController.cs) | [LegacyApplication tests](../tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests) |
|
|
| strict bridge | [LegacyBridgeProtocol.cs](../src/MBN_STOCK_WEBVIEW.LegacyBridge/LegacyBridgeProtocol.cs) | [LegacyBridge tests](../tests/MBN_STOCK_WEBVIEW.LegacyBridge.Tests) |
|
|
| 송출 | [MainWindow.Playout.cs](../src/MBN_STOCK_WEBVIEW.LegacyParityApp/MainWindow.Playout.cs), [playout-ui.js](../src/MBN_STOCK_WEBVIEW.LegacyParityApp/Web/playout-ui.js), [LegacyPlayoutWorkflow.cs](../src/MBN_STOCK_WEBVIEW.Core/Playout/Scenes/LegacyPlayoutWorkflow.cs) | [LegacyPlayoutWorkflowTests.cs](../tests/MBN_STOCK_WEBVIEW.Core.Tests/LegacyPlayoutWorkflowTests.cs) |
|
|
| 수동 목록 | [MainWindow.ManualLists.cs](../src/MBN_STOCK_WEBVIEW.LegacyParityApp/MainWindow.ManualLists.cs), [LegacyManualListsWorkflow.cs](../src/MBN_STOCK_WEBVIEW.LegacyApplication/ManualLists/LegacyManualListsWorkflow.cs) | [manual-list tests](../tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests/ManualLists) |
|
|
| GraphE | [LegacyManualFinancialWorkflow.cs](../src/MBN_STOCK_WEBVIEW.LegacyApplication/ManualFinancial/LegacyManualFinancialWorkflow.cs) | [manual-financial tests](../tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests/ManualFinancial) |
|
|
| PList/AList | [LegacyNamedPlaylistWorkflow.cs](../src/MBN_STOCK_WEBVIEW.LegacyApplication/NamedPlaylist/LegacyNamedPlaylistWorkflow.cs) | [named-playlist tests](../tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests/NamedPlaylist) |
|
|
| ThemeA/EList | [LegacyOperatorCatalogWorkflow.cs](../src/MBN_STOCK_WEBVIEW.LegacyApplication/OperatorCatalog/LegacyOperatorCatalogWorkflow.cs) | [catalog tests](../tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests/OperatorCatalog) |
|
|
| scene matrix | [SCENE_EQUIVALENCE.md](SCENE_EQUIVALENCE.md) | 35/35 builder, 45 alias, PageN matrix |
|
|
| 2026-07-15 증적 | [LEGACY_PARITY_VALIDATION_20260715.md](LEGACY_PARITY_VALIDATION_20260715.md) | 최종 전체 회귀 기록란 포함 |
|
|
|
|
최종 Debug/Release 전체 테스트 개수, 새 MSIX SHA-256와 개발 PGM 결과는 병렬 변경을
|
|
모두 합친 뒤 위 검증 문서에 기록했다. 이전 package 숫자나 hash를 현재 결과로
|
|
재사용하지 않았다.
|