feat: restore legacy drag events and Tornado launch integration

This commit is contained in:
2026-07-15 15:35:25 +09:00
parent 6e0c275fdd
commit 06a16e5741
32 changed files with 3096 additions and 101 deletions

View File

@@ -1,7 +1,10 @@
# LegacyParityApp 이관 검증 증적 — 2026-07-15
이 문서는 현재 `MBN_STOCK_WEBVIEW.LegacyParityApp`의 구현·통합 최종 검증 결과를
한곳에 모은다. 원본
이 문서는 현재 `MBN_STOCK_WEBVIEW.LegacyParityApp`의 구현 증적과 지금 채택할 수 있는
검증 경계를 한곳에 모은다. 2026-07-15의 signed Release MSIX 설치·실행, 실제 마우스/CDP
UI 동작과 제한된 Live 연결 실측은 아래 범위로 기록한다. 병렬 변경 전체의 최종
Debug/Release 및 Web 회귀 수는 확정했지만, scene PREPARE/TAKE IN/PGM 출력은 이 회차의
증적으로 확정하지 않는다. 원본
`C:\Users\MD\source\repos\MBN_STOCK_N`은 모든 조사와 테스트에서 읽기 전용으로
취급했다.
@@ -15,23 +18,79 @@
| Web 경계 | `LegacyBridgeProtocol`의 strict JSON intent allowlist; Web은 DB identity, scene alias, playlist 또는 송출 상태를 확정하지 않음 |
| 벤더 경계 | COM/K3D/Tornado 구현은 `IPlayoutEngine` 뒤에 격리하고 STA 큐와 안전 게이트 유지 |
| 기본 송출 모드 | `DryRun`; Live는 별도 개발 설정, 프로세스별 승인, allowlist와 명령 예산 필요 |
| VS Development Live | 정확한 `--development-live` + Debug + Git 제외 strict 로컬 승인 파일이 모두 맞을 때만 process-scope Live 설정; Release와 오류 경로에서는 이 bootstrap이 Live를 새로 설정하지 않음 |
| 불명확 결과 | timeout/`OutcomeUnknown`에서 자동 재시도·반대 명령·추측 cleanup 금지 |
## 원본 UI와 클릭 이벤트
| 범위 | 현재 증적 |
|---|---|
| Designer 전수 감사 | 원본 Designer 구독 **186개**, 화면별 고유 핸들러 **171개**를 IMPL/DEAD/VISUAL/DRAG/SAFETY로 전수 분류 |
| 업무 탭 | 원본 10개 탭, identity 기반 활성화와 순서 변경 |
| 종목 영역 | 모든 탭에서 유지되는 종목 검색, 결과, 31개 컷과 playlist 추가 |
| 고정/업종/비교 | fixed 328개, 업종 44개, 비교 대상 24개와 action 9개를 폐쇄형 inventory로 고정 |
| 독립 클릭 | UC4 테마, UC6 전문가, UC7 거래정지의 원본 단일 클릭과 UC4/UC6 행별 독립 처리 |
| 전용 입력 | GraphE, FSell, VI, PList, AList, ThemeA, EList를 WebView 화면과 C# intent로 연결 |
| 전용 입력 | GraphE, FSell, VI, PList, AList, ThemeA, EList를 WebView 화면과 C# intent로 연결; ThemeA exact-title 중복확인 포함 |
| 해외 | UC5 해외업종·해외종목·해외지수 조회와 playlist 생성 |
| playlist | 단일/Ctrl/Shift 선택, enabled, 위/아래, 선택/전체 삭제, drag/batch 경계 |
| FarPoint 대체 | Web table로 표현하되 원본 클릭·더블클릭·정렬·이동·저장 이벤트 의미 유지 |
| playlist | 단일/Ctrl/Shift 선택, enabled, 위/아래, 선택/전체 삭제와 비연속 다중 선택 block drag; PREPARED/PROGRAM/pending/unknown 변경 차단 |
| FarPoint 대체 | Web table로 표현하되 원본 클릭·더블클릭·정렬·이동·저장, 탭 dragover 교환과 UC6/EList opaque 행 drag 의미 유지 |
상세 이벤트 대조는 [`OPERATOR_UI_PARITY.md`](OPERATOR_UI_PARITY.md)를 따른다.
### 이벤트·drag·중복확인 구현 증적
줄 단위 원본 근거는
[`LEGACY_DESIGNER_EVENT_PARITY_AUDIT.md`](LEGACY_DESIGNER_EVENT_PARITY_AUDIT.md)에 있다.
186개 구독/171개 고유 핸들러 수에는 선언만 있고 구독되지 않은 메서드를 넣지 않았다.
- playlist drag는 opaque source/target `rowId`와 before/after만 Web에서 보내며, C#이 현재
선택 집합을 표시 순서대로 한 블록으로 이동한다. 체크박스 dragstart는 거부한다.
- 시장 탭은 dragover 대상 진입 시 source/target identity와 예상 인덱스를 보낸다. C# 현재
순서가 예상값과 일치할 때만 교환하므로 같은 전달의 재처리는 no-op이다.
- UC6의 `FpSpread2.AllowDragDrop`은 ThemeA/EList 편집 행의 opaque `rowId` 재정렬로
이관했다. Web이 DB identity나 play index를 정하지 않는다.
- ThemeA `중복확인`은 이름 하나만 strict intent로 받고 C#이 현재 KRX/NXT provider에서
exact title parameterized read를 수행한다. 불명 결과는 `Indeterminate`이고 write나
fallback이 없다. edit 자기 행을 제외하지 않는 원본 규칙도 유지한다.
### 2026-07-15 실제 마우스/CDP UI 검증
설치한 현재 Release package의 실제 WebView 화면에서 다음 운영자 입력을 확인했다.
- `삼성` 검색 결과는 **63개**였고 `삼성출판사`를 선택했다. 컷 3개를 선택한 뒤 한 번의
drop으로 playlist에 정확히 3행을 추가했다.
- playlist 여러 행을 실제 마우스로 선택해 한 블록으로 재정렬했고, enabled 체크박스에서
시작한 drag는 행 이동을 만들지 않았다.
- 시장 탭은 마우스 버튼을 놓기 전 dragover 대상 진입 시 순서가 바뀌었다.
- EList 편집기에서 추천행 **5개**의 순서를 drag로 변경했다.
- ThemeA 목록 **500건**을 불러온 상태에서 `중복확인` exact-title read가 성공했다.
이 과정에서 playlist 행 drop이 조상 `playlist-drop-zone`으로 버블링해 아직 선택된 컷을
추가로 append하던 문제가 발견됐다. drop zone은 이제 exact `legacy-selected-cuts`
`text/plain` token만 허용하고 playlist row custom MIME와 외부 text drop은 무시한다.
따라서 행 재정렬은 `reorder-playlist-rows`만 만들고 `drop-selected-cuts`를 만들지 않는다.
또한 Chromium은 체크박스를 눌러 시작한 경우에도 `dragstart.target`을 가장 가까운 draggable
조상 행으로 보고할 수 있어 단순 `event.target.closest("input")` 검사만으로는 부족했다.
pointerdown 때 checkbox origin의 행 ID를 기억하고 이어지는 row dragstart를 거부하며,
pointerup/pointercancel/dragend에서 상태를 지우도록 수정했다. thin-client 회귀 테스트는
이 Chromium target 형태, 다중 선택 보존, row drop의 ancestor 무시, 외부 text 거부와 exact
selected-cut token의 단일 처리를 실제 DOM event 순서로 고정한다.
### Development Live 시작 구성 증적
공유 `.slnLaunch`와 package launch profile은 LegacyParityApp을 시작 대상으로 고정한다.
[`DEVELOPMENT_LIVE_VISUAL_STUDIO.md`](DEVELOPMENT_LIVE_VISUAL_STUDIO.md)의 strict local
authorization은 `%LOCALAPPDATA%` 아래 Git 제외 파일에 정확한 다섯 속성, 최대 4096바이트,
canonical JSON, exact authorization 문자열과 독립 승인 native/Interop SHA-256을 요구한다.
reparse, 알 수 없는/중복 속성, 파일 및 환경 설정 오류는 fail closed하며 authorization은
process scope에서 마지막에 설정된다. 이 부트스트랩은 기존 trusted Live 이중 게이트,
scene allowlist, asset 검사와 timeout/OutcomeUnknown 정책을 대체하지 않는다.
실제 signed Release MSIX에 `--development-live`를 전달해도 compile-time Debug gate 때문에
승인 파일을 적용하지 않고 `DryRun`으로 실행되는 것을 확인했다. 이 Release DryRun 회차에는
Tornado TCP 연결이 없었고 Network Monitoring 내용도 변하지 않았다.
## 송출 흐름과 장면
| 항목 | 결과 |
@@ -54,6 +113,7 @@
| ThemeA KRX write | **PASS** — create, preview/readback, delete, absence |
| ThemeA NXT write | **PASS** — create, preview/readback, delete, absence |
| EList write | **PASS** — expert create, preview/readback, delete, absence |
| ThemeA 중복확인 read | **PASS** — 500건 목록 상태에서 현재 market의 exact title 조회; DB write 없음 |
개발 DB write는 총 9개 충돌 격리 시나리오다. production service/executor를 사용했으며
인증정보나 DB 원문을 출력하지 않았다. write 결과가 불명확한 경우에는 재시도와 cleanup을
@@ -71,54 +131,50 @@
누락 자산은 Git 또는 MSIX에 임의로 넣지 않는다. 제공되면 같은 trusted-root,
0-byte, reparse, root-escape 검사를 통과한 뒤 해당 scene만 다시 검증한다.
## 개발 Tornado2와 PGM 실측
## 2026-07-15 Tornado2 Live 연결 실측
2026-07-15 개발 Tornado2 `2.7.5.49 x64 DSX10.0 S.R2`의 단일 `30001/tcp`
listener와 K3D x64 등록·라이선스를 고정한 뒤, 설치된 MSIX package-context 앱에서 다음
시퀀스를 실행했다.
아래 시각은 개발 장비 로컬 시간(KST)이다. strict local authorization을 통과한 제한된 Live
회차에서 **14:28:28 HELLO request 1회와 success 1회**, **14:43:10 BYE 1회**를 Network
Monitoring에서 확인했다. 이 회차의 scene 명령, PREPARE, TAKE 계열 명령과 DB write는
각각 **0건**이다.
1. 삼성전자 검색 → `5001` playlist 추가
2. 원본과 같은 fixed leaf **더블클릭**으로 VI 수동 목록 9종목 조회 → `5074` 2페이지 추가
3. `PREPARE 5001 page 1``TAKE IN``OnScenePlayed` → 자동 갱신 정확히 1회
4. `NEXT`로 다음 playlist 항목 `5074 page 1/2` → 자동 갱신 정확히 1회
5. 같은 scene `Page NEXT``5074 page 2/2`의 마지막 4종목 송출
6. `TAKE OUT All``IDLE` → 정상 종료 확인 → `BYE`
종료 후 보관한 Network Monitoring 원문은 **300,007자**, SHA-256
`45858322F60C135858002EB6243D0474F51F1965E23D2332D397C3A3B5DB8049`다. 이 증적은 현재
코드의 Live CONNECT/handshake/DISCONNECT 경로만 확인한다. scene load/mutation,
PREPARE, TAKE IN, NEXT/Page NEXT, TAKE OUT 또는 PGM 화면 출력을 검증했다는 뜻이 아니다.
첫 실측과 화면 증적용 known-outcome 재현은 모두 `OutcomeUnknown=false`로 끝났다. 불명확
결과 뒤 명령 재시도는 없었다. Network Monitoring 최종 원문은 1,863줄,
299,616문자, SHA-256
`7CDB183A2518EF47E5CC3819C2EB14C06492DE0C8E018350A0633E0FBBB5B994`다. 두 실측을
합쳐 `LOAD_SCENE` 4/4, `BEGIN_TRANSACTION` 12/12, `VALUE_SET` 258/258,
`VISIBLE` 568/568, `SCENE_PREPARE` 12/12, `PLAY` 10/10, `STOPAL` 2/2,
`UNLOAD_SCENE` 4/4가 요청/성공으로 일치했고 `SCENE_PLAYED` 10회를 수신했다. 정상
종료에서는 `BYE` 요청 1회가 기록됐고 이후 established 연결과 앱/CDP process는 0개다.
별도의 Release DryRun 회차에서는 `--development-live`가 무시됐고 TCP 연결이 생성되지
않았으며 Network Monitoring 최종 내용도 변하지 않았다. 이는 Debug-only bootstrap 경계를
확인한 결과이지 Release Live 송출 결과가 아니다.
화면 증적은 300프레임 중 72프레임이 검은 기준 화면과 달랐다. `5001` 삼성전자 화면,
`5074` page 1의 5종목, page 2의 4종목을 직접 확인했고 TAKE OUT 뒤 기준 검은 화면으로
복귀했다. 프레임 원본과 Network Monitoring 원문은 실제 컷·운영자료를 Git에 넣지 않는
정책에 따라 로컬 `artifacts/final-validation-20260715`에만 보관한다.
## 현재 package와 범위 회귀 기록
## 최종 전체 회귀 기록
아래 값은 모든 병렬 변경과 fixed-leaf 더블클릭 회귀 테스트가 합쳐진 뒤 다시 실행한
최종값이다.
| 항목 | 최종 결과 |
| 항목 | 2026-07-15 확인 결과 |
|---|---|
| Debug x64 전체 빌드·테스트 | **PASS** — .NET 2,394/2,394, VS 2026 `ParityDebug|x64`, 경고 0·오류 0 |
| Release x64 전체 빌드·테스트 | **PASS** — .NET 2,394/2,394, VS 2026 `Release|x64`, 경고 0·오류 0 |
| Web/LegacyParity 전체 테스트 | **PASS** — 47파일, 435/435 (`Test-WebPlayout` 415 + thin-client 20) |
| Release x64 MSIX 경로·버전 | `artifacts/final-validation-20260715/package/MBN_STOCK_WEBVIEW.LegacyParityApp_0.1.0.0_x64_Test/MBN_STOCK_WEBVIEW.LegacyParityApp_0.1.0.0_x64.msix`, `0.1.0.0`, 15,200,545 bytes |
| 새 MSIX SHA-256·서명 | `FC4EEE1509675E643D2B4CB435E279CAC4611B28534048D9B9EE611E361E0E1F`, **Valid**, `CN=Comtrophy` |
| 설치된 package-context 실행 | **PASS**`Wickedness.MBNStockWebView.LegacyParity_0.1.0.0_x64__qbv3jkvsn3aj0`, DryRun 전체 흐름과 Live 개발 PGM 실행, 정상 종료 |
| 개발 Tornado2 Network Monitoring | **PASS** — HELLO, transaction/mutation/query/prepare/play, event, unload, STOPAL, BYE와 연결 해제 확인 |
| 개발 PGM PREPARE → TAKE IN → NEXT/Page NEXT → TAKE OUT | **PASS**`5001``5074` 1/2 → `5074` 2/2 → 검은 화면 복귀 |
| Gitea main commit/push | **PASS** — 이 최종 검증 문서를 포함한 커밋을 `origin/main`에 fast-forward push |
| signed Release x64 MSIX | **15,213,188 bytes**, SHA-256 `357E17426C45FF516592EE0177D0F95502A98AF084759348ABC191BA5ED21FD5` |
| 설치·실행 | 설치된 package-context 앱 실행 확인 |
| Release `--development-live` | Debug-only gate에 따라 무시되고 `DryRun`; TCP 연결과 Network Monitoring 변경 없음 |
| 실제 마우스/CDP UI 범위 | 삼성 63건 검색→삼성출판사→3컷 단일 drop, playlist multi-row reorder, checkbox drag 차단, tab dragover 교환, EList 5행 reorder, EList 수량 입력칸 origin drag 차단, ThemeA 500건과 중복확인 read 확인 |
| 제한된 Live Network 범위 | HELLO request/success 각 1회와 BYE 1회; scene/PREPARE/TAKE/DB write 0 |
| .NET Debug/Release x64 | 각 **2,446/2,446**, 실패·건너뜀 0, 경고·오류 0 |
| Web Node 회귀 | **438/438**, 실패·취소·건너뜀·TODO 0 |
| Visual Studio 2026 build | VS 18.7.3 / MSBuild 18.7.8, Debug·Release x64 모두 경고 0 / 오류 0 |
playlist drop MIME 경계, checkbox-origin drag와 EList input/button-origin drag 회귀는
thin-client DOM event 테스트로도 고정했다. 설치된 최종 Release package에서 실제 마우스로
EList 수량 입력칸에서 시작한 drag가 순서를 바꾸지 않고, 같은 행의 비입력 영역에서 시작한
drag만 5행 순서를 바꾸는 것도 확인했다. .NET Debug/Release 합계 4,892회와 Web 438회를
모두 통과했지만,
scene PREPARE/TAKE/PGM을 실행하지 않은 이 Live 연결 회차를 전체 방송 송출 PASS로 확대하지
않는다.
## 판정 경계
- 35/35 자동 장면 동등성은 모든 장면의 실제 PGM 출력 완료를 뜻하지 않는다.
- 33/33 DB selector 통과는 외부 영상/배경 자산 준비를 뜻하지 않는다.
- 개발 DB 9개 write 시나리오 통과는 운영 DB에 쓰기를 허용하거나 수행했다는 뜻이 아니다.
- 개발 Tornado2/PGM은 안전 게이트를 유지한 별도 통합 검증 대상이다.
- 현재 signed Release MSIX의 설치·DryRun 실행, 실제 마우스/CDP UI, .NET Debug/Release
각 2,446개와 Web 438개 회귀를 확인했다.
- Live 증적은 HELLO/BYE만 확인한 연결 회차다. scene/PREPARE/TAKE/DB write가 0이므로
PGM 송출 또는 원클릭 TAKE IN 전체 통합 성공으로 판정하지 않는다.
- 원본 저장소, 실제 Cuts, 라이선스와 인증정보는 계속 읽기 전용 또는 Git 외부로 유지한다.