feat: align legacy operator and playout behavior
This commit is contained in:
288
docs/LEGACY_BEHAVIOR_PARITY_WORKLOG.md
Normal file
288
docs/LEGACY_BEHAVIOR_PARITY_WORKLOG.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# MBN_STOCK_N 동작 동등성 작업 기록
|
||||
|
||||
기준 원본은 `C:\Users\MD\source\repos\MBN_STOCK_N`이며 항상 읽기 전용으로 분석한다.
|
||||
대상은 `MBN_STOCK_WEBVIEW.LegacyParityApp`이다. 화면 모양만 비슷하게 만드는 것이 아니라
|
||||
원본 이벤트의 입력, 상태 전이, DB 조회, K3D 호출 순서와 화면 피드백을 코드 단위로 대조한다.
|
||||
|
||||
## 판정 절차
|
||||
|
||||
1. 원본 이벤트 핸들러와 관련 필드를 추적한다.
|
||||
2. 정상 동작과 명백한 원본 버그를 구분한다.
|
||||
3. 정상 동작을 특성화 테스트로 고정한다.
|
||||
4. C# authoritative state와 WebView 표시를 함께 수정한다.
|
||||
5. 안전 게이트를 유지한 상태에서 Debug/Release x64 빌드와 전체 테스트를 실행한다.
|
||||
|
||||
## Slice 1: 선택 행, F8, NEXT, TAKE OUT
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:1078` `PlayList_CellClick`
|
||||
- `MainForm.cs:1915` `btnTake_Click`
|
||||
- `MainForm.cs:2020` `ONAirMode`
|
||||
- `MainForm.cs:2081` `btnTakeout_Click`
|
||||
- `MainForm.cs:2120` `btnNext_Click`
|
||||
- `MainForm.cs:2176` `Next_Scene`
|
||||
- `MainForm.cs:2495` `Show_PlayList`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- 운영자 선택 후보와 현재 PROGRAM 행을 독립 상태로 유지한다.
|
||||
- F8은 IDLE/PREPARED/PROGRAM 모두에서 그 시점의 선택 후보를 새로 Prepare 후 Play한다.
|
||||
- 선택 후보가 비활성이면 아래 방향의 첫 활성 행만 사용하며 순환하지 않는다.
|
||||
- NEXT는 선택 후보가 아니라 현재 PROGRAM 행을 기준으로 Page NEXT 또는 다음 활성 컷을 결정한다.
|
||||
- 선택 후보는 회색, 현재 PROGRAM 행은 분홍으로 동시에 표시한다.
|
||||
- Page NEXT 결과를 상태 표시뿐 아니라 플레이리스트 Page 셀에도 남긴다.
|
||||
- F8/PREPARE는 페이지형 컷을 page 1로 다시 시작한다.
|
||||
- 마지막 항목에서는 NEXT를 비활성화하고 vendor 명령을 보내지 않는다.
|
||||
- TAKE OUT은 `StopAll` 범위이며 IDLE에서도 연결된 엔진에 긴급 정리를 요청할 수 있다.
|
||||
|
||||
남은 세부 감사:
|
||||
|
||||
- Page 총수를 행에 저장된 원본 `current/total`에서 고정할지, PREPARE 시 fresh DB 결과로
|
||||
다시 계산할지에 대한 동등성은 PageN 전용 Slice에서 다시 판정한다.
|
||||
- column 3의 정확한 한글 문자열로 Page NEXT를 분기하던 원본 조건과 typed builder
|
||||
식별자를 사용하는 현재 조건의 차이도 같은 Slice에서 검증한다.
|
||||
|
||||
유지한 안전 차이:
|
||||
|
||||
- SDK Play callback이 pending이면 TAKE OUT 이외 명령을 보내지 않는다.
|
||||
- timeout 또는 `OutcomeUnknown`은 재시도하지 않고 격리한다.
|
||||
- 결과가 확인되기 전에는 새 PROGRAM 행으로 확정하지 않는다.
|
||||
- PROGRAM 중 구조 변경은 현재 단계에서 계속 차단한다.
|
||||
|
||||
재현하지 않은 원본 버그:
|
||||
|
||||
- 마지막 컷에서 이전 `m_ShowIndex` 때문에 같은 scene을 다시 Play하는 동작
|
||||
- 빈 `catch`로 잘못된 page 문자열과 DB/엔진 오류를 숨기는 동작
|
||||
- Home/End가 timer와 색상 상태를 깨뜨리는 동작
|
||||
- 고정 크기 `s_buf[100]`에 의존하는 Space 토글
|
||||
|
||||
## Slice 2: 체크박스, 키보드, 이동, 삭제, drag
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 변경을 합친 package의 실제 마우스 검증은 아직 수행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:1078` `PlayList_CellClick`
|
||||
- `MainForm.cs:1464` `btnup_Click`
|
||||
- `MainForm.cs:1579` `Spd_UpDown`
|
||||
- `MainForm.cs:1649` `btndelete_Click`
|
||||
- `MainForm.cs:1730` `btntdelete_Click`
|
||||
- `MainForm.cs:3140` `checkBox1_CheckedChanged`
|
||||
- `MainForm.cs:3622` `PlayList_DragDrop`
|
||||
- `MainForm.cs:3805` `PlayList_KeyPress`
|
||||
- `MainForm.cs:3887` `ProcessCmdKey`
|
||||
- `MainForm.Designer.cs:1598` `AllowRowMove`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- F8 후보, 현재 PROGRAM, Ctrl/Shift 삭제 집합과 키보드 focus를 서로 다른 상태로 유지한다.
|
||||
- F8/PREPARE 성공과 disabled-row skip은 실제 F8 후보만 옮기며 `bufdel`에 해당하는 삭제 집합과
|
||||
`m_newrow/m_oldrow`에 해당하는 Shift anchor를 바꾸지 않는다.
|
||||
- 체크박스 열을 누르면 삭제 집합과 Shift anchor는 그대로 두고 그 행만 F8 후보가 된다.
|
||||
- Space는 포커스된 실제 행의 현재 enabled 값을 반전하며 별도 100칸 배열을 사용하지 않는다.
|
||||
- Home/End는 원본처럼 자동 갱신 timer를 먼저 멈추고 첫/마지막 행을 후보로 만들며 그 행으로
|
||||
scroll한다. DEL 집합, Shift anchor, 현재 입력/ListBox focus, PROGRAM과 Page 표시는 바꾸지 않는다.
|
||||
- Up/Down은 마지막 활성 행 하나만 인접 행과 교환하고 이동한 stable row identity를 후보로 유지한다.
|
||||
- playlist drag도 Ctrl/Shift 집합 전체가 아니라 실제 drag source 한 행만 before/after로 이동한다.
|
||||
- 선택 삭제 뒤 기존 후보가 살아 있으면 같은 row identity를 유지하고, 삭제됐으면 다음 생존 행,
|
||||
다음 행이 없으면 이전 생존 행을 명시적인 후보로 만든다.
|
||||
- 빈 목록에 첫 행을 추가하거나 DB playlist를 불러오면 첫 행이 화면의 후보와 실제 F8 대상이
|
||||
되지만, 실제 행 클릭 전에는 DEL 집합에 암묵적으로 들어가지 않는다. 화면에 후보가 없는데
|
||||
내부적으로 0번 행을 재생하던 fallback은 제거했다.
|
||||
- PREPARED/PROGRAM/pending/OutcomeUnknown에서는 enabled, 이동, 삭제, row/cut drag를 Web과
|
||||
native 양쪽에서 차단한다. 후보 선택과 컷 단순 선택은 로컬 상태이므로 허용한다.
|
||||
|
||||
재현하지 않은 원본 버그:
|
||||
|
||||
- `bufdel[100]`, `s_buf[100]`의 제한, 중복 인덱스와 stale selection
|
||||
- 선택 삭제가 PROGRAM 행이나 현재 송출 순서를 손상하는 동작
|
||||
- 이동·삭제 뒤 `m_rowidx`, `m_newrow`, `m_crow` 숫자 인덱스가 다른 항목을 가리키는 동작
|
||||
- Home/End 뒤 focus와 후보가 서로 다른 행을 가리키는 동작
|
||||
- 컷 drag payload를 무시하거나 playlist 다중 삭제 집합을 다중 행 drag로 확대하는 동작
|
||||
|
||||
## Slice 3: PageN 문자열 분기와 저장 total
|
||||
|
||||
상태: 코드 및 집중 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:2120` `btnNext_Click`
|
||||
- `MainForm.cs:2176` `Next_Scene`
|
||||
- `MainForm.cs:2495` `Show_PlayList`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- 실제 operator row는 column 3 `GraphicType`이 정확히 `5단 표그래프`, `6종목 현재가`,
|
||||
`12종목 현재가`일 때만 같은 행 Page NEXT를 허용한다.
|
||||
- 위 세 문자열은 각각 5·6·12개 단위와 연결하고, row의 `current/total` 중 total을
|
||||
1..20 범위에서 읽어 PREPARE부터 TAKE OUT까지 고정한다.
|
||||
- on-air 중 DB 결과가 늘어도 저장 total 뒤의 새 page를 노출하지 않고, 줄어든 결과로
|
||||
현재/다음 page를 만들 수 없으면 vendor NEXT 전에 fail-closed한다.
|
||||
- 같은 paged scene code로 해석되더라도 column 3 문자열이 정확하지 않으면 일반 행으로
|
||||
취급하여 다음 enabled playlist row로 이동한다.
|
||||
- paged row의 마지막 page에서 다음 playlist row로 이동한 뒤에는 원본처럼 자동 갱신을
|
||||
다시 시작하지 않는다. 일반 행에서 다음 row로 이동한 경우에만 다시 시작한다.
|
||||
- 요구사항의 20 page cap, 0건 fail-closed, 마지막 row에서 재생 반복 금지는 유지한다.
|
||||
원본의 101/121/241건에서 21 page가 되는 off-by-one, `1/0`, stale scene 재생은 명시적인
|
||||
안전 교정이며 문자 그대로 재현하지 않는다.
|
||||
- timer refresh의 새 구현은 불완전할 수 있는 `GetPlayingScene` proxy가 아니라 adapter가
|
||||
보존한 on-air scene reference를 갱신한다는 실제 구현 계약으로 주석과 테스트 이름을 바로잡았다.
|
||||
|
||||
자동 검증:
|
||||
|
||||
- Core: 저장 total 고정, DB 증가/감소, refresh, 없는 다음 page 차단, 비정규 문자열의
|
||||
playlist NEXT, metadata 없는 직접 Core 호출의 기존 fresh-plan 동작.
|
||||
- Application: 세 정확한 문자열의 5·6·12 mapping, suffix total 고정, 유사 문자열 차단,
|
||||
0/21 및 잘못된 `current/total` 거부.
|
||||
|
||||
## Slice 4: 종목 검색과 런타임 컷 메뉴
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:399`, `MainForm.cs:596` `종목.ini`/`INI_parser`
|
||||
- `MainForm.cs:844` `button9_Click`
|
||||
- `MainForm.cs:859` `searchStock`
|
||||
- `MainForm.cs:3039` `txtSearch_KeyPress`
|
||||
- `MainForm.cs:3427` `listView1_MouseDown`
|
||||
- `MainForm.cs:3534` `listView1_MouseMove`
|
||||
- `MainForm.cs:3635` `PlayList_DragDrop`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- 빈 검색 버튼은 기존 결과·종목 선택·컷 선택을 유지하고, 빈 Enter만 전체 조회를 실행한다.
|
||||
- 새 조회는 종목 선택만 지우며 컷 선택은 유지한다. 이후 새 종목을 선택해 drag/drop하면
|
||||
남아 있던 컷을 새 종목 identity로 추가한다.
|
||||
- 컷 Ctrl/Shift, 정확한 좌표와 500ms 미만 double-click, 물리 순서 drag, 중복 drop을 유지한다.
|
||||
- 일반 시장의 `-` 구분행은 무시하고 NXT에서는 원본 경고 modal 순서를 유지한다.
|
||||
- CP949 `종목.ini`는 `%LOCALAPPDATA%` 외부 override, package 후보 순서로 읽는다. 둘 다 없거나
|
||||
유효하지 않으면 검증된 31개 내장 목록으로 폴백한다. 외부 파일이 존재하지만 잘못됐으면
|
||||
다른 파일로 우회하지 않고 즉시 폴백한다.
|
||||
- 최대 50개 물리행, 64 KiB, 정확한 `[종목]`, 알려진 컷과 구분행만 허용하고 부분 결과와
|
||||
실제 경로는 상태에 노출하지 않는다. 실제 INI 파일은 Git/MSIX에 넣지 않는다.
|
||||
- 런타임 권위본은 원본의 `bin\Debug\Res\종목.ini`이며 현재 내장 fallback과 SHA-256이 같다.
|
||||
이 파일과 `s5001`이 사용하는 `시간외단일가`를 유지한다. 소스 트리 `Res\종목.ini`의
|
||||
서로 다른 `시간외종가` 표기는 실제 실행 기준으로 되돌리지 않는다.
|
||||
|
||||
## Slice 5: 전역 키, 배경과 DissolveTime
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:964` `MainForm_KeyDown`
|
||||
- `MainForm.cs:3887` `ProcessCmdKey`
|
||||
- `MainForm.cs:4008` `btnback_Click`
|
||||
- `MainForm.cs:4037` `ckback_CheckedChanged`
|
||||
- `MainForm.cs:488`, `MainForm.cs:1059` `ComboDi.SelectedIndex`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- modal에서 처리한 Escape/F2/F3/F8은 뒤의 playout key listener로 전달하지 않는다. 이름 있는
|
||||
playlist modal의 Escape는 modal만 닫고 TAKE OUT을 보내지 않는다.
|
||||
- Delete는 원본 KeyPreview처럼 playlist 삭제 intent를 보내되 기본 키를 억제하지 않아,
|
||||
텍스트 입력이 focus를 가진 경우 문자 삭제도 정상 동작한다.
|
||||
- 시작 기본 배경은 `Cuts\video\기본.vrv`, F2/F3 기본 배경은 `Cuts\..\배경\기본.vrv`로
|
||||
분리하고 두 신뢰 root가 서로 대신하지 않게 했다.
|
||||
- 화면에 원본 `DissolveTime` 1..20을 표시하고 C#에는 인덱스 0..19만 보낸다. 기본 인덱스는
|
||||
6(화면 7)이며, PREPARE/F8 때 현재 전역 인덱스를 기존 playlist 모든 entry에 적용한다.
|
||||
행 생성 시 저장된 기본 6이 전역 선택을 덮어쓰던 경로는 제거했다.
|
||||
|
||||
## Slice 6: 탭 재진입과 dragover 교환
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 DB 쓰기/Tornado 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:2937` `tabControl1_SelectedIndexChanged`
|
||||
- `MainForm.cs:3183` `tab_MouseDown`
|
||||
- `MainForm.cs:3214` `tab_MouseMove`
|
||||
- `MainForm.cs:3234` `tab_DragOver`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- 코스피/코스닥은 재진입마다 목록을 다시 조회하고 선택·비교쌍을 초기화한다.
|
||||
- 비교는 저장된 CP949 비교쌍을 매 재진입마다 다시 읽고, 해외종목·거래정지는 검색 결과,
|
||||
선택과 정렬 상태를 새 UC처럼 초기화한다. MA5/MA20은 MainForm 전역 상태로 유지한다.
|
||||
- 폐기된 탭의 늦은 비동기 결과는 activation generation으로 무시한다.
|
||||
- 좁은 탭을 넓은 탭 오른쪽으로 옮길 때는 `target.right - source.width` 초과, 왼쪽은
|
||||
`target.left + source.width` 미만에서만 교환한다. 같거나 넓은 source는 진입 즉시 교환한다.
|
||||
- 같은 crossing의 반복 dragover와 busy 거부가 중복/누락 교환을 만들지 않게 했다.
|
||||
|
||||
## Slice 7: UC1 트리 이벤트와 이동평균선 샘플링
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `Control/UC1.cs:121-139`, `1303-1340`, `1393-1407`
|
||||
- `Scene/s8010.cs:431-453`, `765-807`
|
||||
- `MainForm.cs:2495+`, `2937-2999`
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- 루트 라벨 단일 클릭은 선택만 하며 disclosure만 펼침/접기를 수행한다.
|
||||
- 루트·리프 더블클릭은 펼침 상태를 바꾸지 않고 C# action을 정확히 한 번 보낸다.
|
||||
- 같은 탭의 상태 render는 open/selection/focus/scroll을 보존하고, 실제 탭 재진입은 원본 UC1처럼
|
||||
Expand=true, 전체 펼침, 첫 루트 선택·focus·scroll로 초기화한다.
|
||||
- Busy/고정 섹션 배치 중에는 action뿐 아니라 로컬 disclosure 변경도 차단한다.
|
||||
- MA5/MA20은 행 추가 시 저장하지 않고 s8010 PREPARE, F8, NEXT, 자동 갱신의 장면 생성 시점마다
|
||||
현재 전역 체크 상태를 다시 읽는다. named playlist identity에도 MA 플래그를 저장하지 않는다.
|
||||
- 원본의 `uc_3 == null` 부모 더블클릭 예외와 부분 추가 버그는 재현하지 않는 명시적 결함 제외다.
|
||||
|
||||
상세 근거와 결함 제외는 `docs/UC1_TREE_EVENT_PARITY.md`에 기록했다.
|
||||
|
||||
## Slice 8: K3D 원시 인자, callback 게이트, 컷 ListView 경계
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
|
||||
|
||||
원본 근거:
|
||||
|
||||
- `MainForm.cs:2628-2639` 공통 `SetBackgroundVideo(path, 2004, 10)`
|
||||
- `MainForm.cs`의 `SetSceneEffectType(10, FADE, ComboDi.SelectedIndex)` 호출
|
||||
- `MainForm.cs:2651+`, `1992`의 3,000,000초 Int32 곱셈/Timer.Interval 결과
|
||||
- `MainForm.cs:3427-3525`, `3566-3588`, `3635-3643` 컷 선택·드롭
|
||||
- `MainForm.cs:3887-3906` Home/End 후 base key 처리
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- K3D scene effect 첫 인자는 bool로 정규화하지 않고 원본 raw `10`을 전달한다.
|
||||
- 공통 배경은 raw `(2004, 10)`, s5006 장면 내부 배경은 `(1, 1)`을 구분한다.
|
||||
- OnCutOut/OnStopAll 완료 전에는 PREPARE, F8, NEXT, 갱신과 중복 TAKE OUT을 SDK에 보내지 않고
|
||||
Web 버튼과 운영 데이터 mutation도 잠근다. pending Play 중 비상 TAKE OUT 1회 경로는 유지한다.
|
||||
- 5076/5079/5080/5081은 원본의 unchecked Int32 overflow 뒤 Timer.Interval 예외 때문에 실제 자동
|
||||
갱신이 시작되지 않으므로 34.7일 타이머로 바꾸지 않고 명시적으로 비활성화한다.
|
||||
- 런타임 컷 메뉴 bridge 범위를 원본 물리행과 같은 0..49로 통일한다.
|
||||
- 연속 Shift/Ctrl+Shift는 Win32 ListView selection mark를 고정하고, 빈 영역 클릭은 선택을 지우되
|
||||
native anchor는 유지한다. 빈 영역 뒤 stale 컷 drop은 0행이다.
|
||||
- PREPARED/PROGRAM 중 컷 pointer double-click의 암묵적 행 추가를 native/Web 양쪽에서 차단한다.
|
||||
- Home/End의 playlist/timer 부작용 뒤에도 검색 입력 caret과 검색결과 기본 선택 동작을 보존한다.
|
||||
- DissolveTime의 모든 외부·Core 경계는 원본 ComboBox index인 0..19만 허용한다.
|
||||
|
||||
## Slice 9: 종목 ListBox와 컷 ListView 키보드
|
||||
|
||||
상태: 코드 및 자동 테스트 완료, 실제 package 키보드 검증은 아직 수행하지 않음.
|
||||
|
||||
고정한 계약:
|
||||
|
||||
- 종목 결과는 하나의 tab stop을 갖고 ArrowUp/Down, Home/End와 10행 viewport의
|
||||
PageUp/PageDown 9행 이동을 재현한다. double-click도 선택만 바꾸고 컷을 추가하지 않는다.
|
||||
- 종목 Home/End는 결과 선택과 focus를 보존한 채 전역 playlist 경계 부수 효과도 실행한다.
|
||||
- 컷 목록은 물리행과 separator를 포함해 ArrowUp/Down, Home/End, Space를 처리한다.
|
||||
- 컷 Ctrl/Shift/Ctrl+Shift는 Win32 selection mark 규칙을 C# 권위 상태에서 적용하고 키보드
|
||||
선택은 마우스 전용 `selectedRowsList`에 섞지 않는다. drop은 native 선택 집합을 사용한다.
|
||||
- PREPARED/PROGRAM/pending/unknown 또는 종목 미선택 상태에서는 컷 선택과 focus 이동을 함께
|
||||
차단해 DOM과 C# 상태가 어긋나지 않게 한다.
|
||||
|
||||
## 다음 대조 순서
|
||||
|
||||
1. 컷 ListView의 drag 시작 임계값과 실제 package 키보드 회귀
|
||||
2. GraphE/FSell/VI/PList/AList/ThemeA/EList modal별 남은 키·focus·취소 이벤트
|
||||
3. 자동 갱신 UI의 실제 실행 상태 기반 통합 테스트와 종료 상태 행렬
|
||||
4. Debug/Release x64와 새 package에서 실제 마우스·키보드·drag 회귀
|
||||
|
||||
각 항목은 실제 원본 코드 근거와 회귀 테스트가 모두 생기기 전에는 완료로 판정하지 않는다.
|
||||
@@ -49,10 +49,10 @@ HELLO/BYE 연결은 2026-07-15에 확인했다. scene/PREPARE/TAKE/PGM은 실행
|
||||
| LF-014 | P1 | ThemeA/EList | 완료 | 검색·신규·편집·삭제·종목 구성·Delete key, ThemeA exact-title 중복확인, EList opaque `rowId` drag 재정렬 이관. 실제 UI에서 ThemeA 500건 중복 read와 EList 5행 reorder 확인 | scene PGM은 LF-005 |
|
||||
| LF-015 | P1 | UC5 해외 | 완료 | 해외업종·해외종목·해외지수 조회와 playlist 경로 이관 | s6001 13개 영상은 LF-001 |
|
||||
| LF-016 | P1 | F2/F3 기본 배경 | 외부자산 필요 | sibling background root, picker, background-none와 trusted path 검사는 완료. 이 장비에 `기본.vrv`가 없음 | 제공 전 fail closed |
|
||||
| LF-017 | P1 | 시스템 종료 확인 | 완료 | 기본 취소 확인창, 중립 문구, 종료/취소 두 버튼과 자동 TAKE OUT/반대 명령 금지 경계를 코드·자동 테스트로 유지 | 현재 Release package-context 회귀는 LF-005와 함께 판정 |
|
||||
| LF-017 | P1 | 시스템 종료 확인 | 완료 | 원본 제목 `매일경제TV`, 중립 문구, 예/아니요와 기본 아니요를 유지하며 자동 TAKE OUT/반대 명령 금지 경계를 코드·자동 테스트로 유지 | 현재 Release package-context 회귀는 LF-005와 함께 판정 |
|
||||
| LF-018 | P1 | 원본 비교쌍/수동 파일 | 완료 | `종목비교.dat` 8쌍 1회 import·재시작 marker, FSell 3×5행과 VI 9항목 trusted parser/store 검증 | 원본 파일은 계속 읽기 전용 |
|
||||
| LF-019 | P1 | 실제 DB read→DTO→mutation | 완료 | Oracle/MariaDB 필수 scene selector 33/33, 전체 query 58건 PASS | 외부 asset/PGM 성공과 별도 |
|
||||
| LF-020 | P2 | FarPoint 대체 | 완료 | 실제 마우스로 playlist 다중행 reorder·checkbox 차단·탭 release 전 dragover 교환·EList reorder 확인. row drop 버블링의 cut append와 Chromium checkbox-origin target 문제를 수정하고 DOM 회귀로 고정 | 활성 Print/Excel export는 원본에도 없음 |
|
||||
| LF-020 | P2 | FarPoint 대체 | 현재 빌드 실측 제외 | source 단일 행 drag, F8 후보·PROGRAM·DEL 집합·Shift anchor 분리, 종목 ListBox와 컷 ListView 키보드/selection mark를 DOM/C# 회귀로 고정. 탭 dragover와 EList reorder는 기존 실측 유지 | 현재 변경을 합친 package에서 playlist 단일 행 drag와 키보드/focus를 다시 실측; 활성 Print/Excel export는 원본에도 없음 |
|
||||
| LF-021 | P2 | 패키지 단일 인스턴스·모달 격리 | 완료 | 두 번째 activation 전달, 강제 종료 없음. modal에서 F2/F3/F8/Escape와 playlist key 격리 | 새 MSIX 전체 회귀 |
|
||||
| LF-022 | P2 | 영구 운영 로그/bridge 오류 | 완료 | bounded/redacted log와 toast, malformed payload가 송출 상태를 바꾸지 않음 | authoritative Network Monitoring과 함께 판독 |
|
||||
| LF-023 | P1 | NXT 저장 playlist restore | 외부 데이터 정합성 | stale code를 제목/current identity로 재검증하는 닫힌 경로는 완료. 현재 `v_all_stock` 결과가 비어 preview를 fail closed | DBA/data 정합성 없이는 direct-table 추정 금지 |
|
||||
@@ -69,9 +69,9 @@ intent를 만들지 않는 것이 원본과 동등하다. 종료의 자동 `Stop
|
||||
|
||||
현재 `DRAG` 구현은 다음 세 C# 권위 경로를 포함한다.
|
||||
|
||||
- playlist 행 drag는 선택된 비연속 행도 현재 표시 순서를 보존한 한 블록으로 임의
|
||||
before/after 위치에 옮긴다. 체크박스에서 시작한 drag와 PREPARED/PROGRAM/pending/unknown
|
||||
상태의 변경은 차단한다.
|
||||
- playlist 행 drag는 FarPoint `AllowRowMove`의 source 행 하나를 임의 before/after 위치로
|
||||
옮긴다. Ctrl/Shift 집합은 삭제 선택으로만 유지하며, 체크박스에서 시작한 drag와
|
||||
PREPARED/PROGRAM/pending/unknown 상태의 변경은 차단한다.
|
||||
- 시장 탭은 drop 때가 아니라 dragover 대상에 들어갈 때 교환한다. source/target identity와
|
||||
예상 두 인덱스가 C# 현재 순서와 일치할 때만 적용해 중복 메시지가 순서를 되돌리지 않는다.
|
||||
- 원본 UC6 `FpSpread2.AllowDragDrop`은 ThemeA/EList 편집행의 opaque `rowId` drag로
|
||||
@@ -82,6 +82,12 @@ ThemeA의 원본 `중복확인`도 이름만 담은 strict intent로 연결했
|
||||
결과 불명은 `Indeterminate`이며 write나 추정 fallback이 없다. 원본처럼 edit 자기 행을
|
||||
제외하지 않아 변경하지 않은 이름은 중복으로 판정된다.
|
||||
|
||||
명백한 결함 또는 송출 안전을 해치는 동작은 동등성 예외로 유지한다. 마지막 행에 NEXT로
|
||||
도착한 뒤 stale `m_ShowIndex` 때문에 같은 scene을 다시 `Play(10)`하는 동작, 실패한 Page NEXT를
|
||||
성공 전에 화면 숫자에 반영하는 동작, `s_buf[100]` shadow 값 때문에 첫 Space가 실제 체크값과
|
||||
어긋나는 동작은 재현하지 않는다. 마지막 NEXT는 no-wrap/no-command, Page 표시는 성공 뒤 반영,
|
||||
Space는 현재 authoritative enabled 값을 반전한다.
|
||||
|
||||
## Development Live strict local authorization
|
||||
|
||||
[`DEVELOPMENT_LIVE_VISUAL_STUDIO.md`](DEVELOPMENT_LIVE_VISUAL_STUDIO.md)의 시작 구성은
|
||||
|
||||
@@ -32,7 +32,7 @@ Debug/Release 및 Web 회귀 수는 확정했지만, scene PREPARE/TAKE IN/PGM
|
||||
| 독립 클릭 | UC4 테마, UC6 전문가, UC7 거래정지의 원본 단일 클릭과 UC4/UC6 행별 독립 처리 |
|
||||
| 전용 입력 | GraphE, FSell, VI, PList, AList, ThemeA, EList를 WebView 화면과 C# intent로 연결; ThemeA exact-title 중복확인 포함 |
|
||||
| 해외 | UC5 해외업종·해외종목·해외지수 조회와 playlist 생성 |
|
||||
| playlist | 단일/Ctrl/Shift 선택, enabled, 위/아래, 선택/전체 삭제와 비연속 다중 선택 block drag; PREPARED/PROGRAM/pending/unknown 변경 차단 |
|
||||
| playlist | F8 후보와 Ctrl/Shift 삭제 선택 분리, enabled, 위/아래, 선택/전체 삭제와 drag source 단일 행 이동; PREPARED/PROGRAM/pending/unknown 변경 차단 |
|
||||
| FarPoint 대체 | Web table로 표현하되 원본 클릭·더블클릭·정렬·이동·저장, 탭 dragover 교환과 UC6/EList opaque 행 drag 의미 유지 |
|
||||
|
||||
상세 이벤트 대조는 [`OPERATOR_UI_PARITY.md`](OPERATOR_UI_PARITY.md)를 따른다.
|
||||
@@ -43,8 +43,8 @@ Debug/Release 및 Web 회귀 수는 확정했지만, scene PREPARE/TAKE IN/PGM
|
||||
[`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는 거부한다.
|
||||
- playlist drag는 opaque source/target `rowId`와 before/after만 Web에서 보내며, C#이 실제
|
||||
drag source 한 행만 이동한다. Ctrl/Shift 집합은 삭제 선택으로 유지하고 체크박스 dragstart는 거부한다.
|
||||
- 시장 탭은 dragover 대상 진입 시 source/target identity와 예상 인덱스를 보낸다. C# 현재
|
||||
순서가 예상값과 일치할 때만 교환하므로 같은 전달의 재처리는 no-op이다.
|
||||
- UC6의 `FpSpread2.AllowDragDrop`은 ThemeA/EList 편집 행의 opaque `rowId` 재정렬로
|
||||
@@ -59,8 +59,10 @@ Debug/Release 및 Web 회귀 수는 확정했지만, scene PREPARE/TAKE IN/PGM
|
||||
|
||||
- `삼성` 검색 결과는 **63개**였고 `삼성출판사`를 선택했다. 컷 3개를 선택한 뒤 한 번의
|
||||
drop으로 playlist에 정확히 3행을 추가했다.
|
||||
- playlist 여러 행을 실제 마우스로 선택해 한 블록으로 재정렬했고, enabled 체크박스에서
|
||||
시작한 drag는 행 이동을 만들지 않았다.
|
||||
- 당시 package에서는 playlist 여러 행을 한 블록으로 재정렬했고, enabled 체크박스에서
|
||||
시작한 drag는 행 이동을 만들지 않았다. 이후 원본 `AllowRowMove` 재감사에서 블록 이동은
|
||||
근거 없는 확장으로 판정되어 현재 코드는 drag source 한 행만 이동하도록 교정됐다. 따라서
|
||||
이 항목의 과거 block-drag 실측은 현재 단일 행 drag의 package 실측으로 재사용하지 않는다.
|
||||
- 시장 탭은 마우스 버튼을 놓기 전 dragover 대상 진입 시 순서가 바뀌었다.
|
||||
- EList 편집기에서 추천행 **5개**의 순서를 drag로 변경했다.
|
||||
- ThemeA 목록 **500건**을 불러온 상태에서 `중복확인` exact-title read가 성공했다.
|
||||
@@ -74,7 +76,7 @@ Debug/Release 및 Web 회귀 수는 확정했지만, scene PREPARE/TAKE IN/PGM
|
||||
조상 행으로 보고할 수 있어 단순 `event.target.closest("input")` 검사만으로는 부족했다.
|
||||
pointerdown 때 checkbox origin의 행 ID를 기억하고 이어지는 row dragstart를 거부하며,
|
||||
pointerup/pointercancel/dragend에서 상태를 지우도록 수정했다. thin-client 회귀 테스트는
|
||||
이 Chromium target 형태, 다중 선택 보존, row drop의 ancestor 무시, 외부 text 거부와 exact
|
||||
이 Chromium target 형태, drag source 한 행만 전송, row drop의 ancestor 무시, 외부 text 거부와 exact
|
||||
selected-cut token의 단일 처리를 실제 DOM event 순서로 고정한다.
|
||||
|
||||
### Development Live 시작 구성 증적
|
||||
@@ -168,6 +170,26 @@ drag만 5행 순서를 바꾸는 것도 확인했다. .NET Debug/Release 합계
|
||||
scene PREPARE/TAKE/PGM을 실행하지 않은 이 Live 연결 회차를 전체 방송 송출 PASS로 확대하지
|
||||
않는다.
|
||||
|
||||
### 코드 대조 Slice 1~9 합본 추가 검증
|
||||
|
||||
이후 원본 이벤트를 다시 코드 단위로 대조해 F8 후보·PROGRAM·DEL 다중선택·Shift anchor를
|
||||
분리하고, 런타임 컷 50행, 종목/컷 키보드, UC1 tree, s8010 이동평균 재샘플링, K3D raw 인자와
|
||||
OnCutOut/OnStopAll callback 게이트를 합쳤다. 이 합본의 검증 결과는 다음과 같다.
|
||||
|
||||
| 항목 | 추가 검증 결과 |
|
||||
|---|---|
|
||||
| .NET Debug x64 | **2,584/2,584** 통과, 실패·건너뜀 0 |
|
||||
| .NET Release x64 | **2,584/2,584** 통과, 실패·건너뜀 0 |
|
||||
| Web Node | **456/456** 통과, 실패·취소·건너뜀·TODO 0 |
|
||||
| 솔루션 Debug/Release x64 | NuGet x64 복원 후 각각 경고 0 / 오류 0 |
|
||||
| Visual Studio 2026 MSBuild | VS **18.7.4**의 x64 MSBuild로 Debug/Release 솔루션 빌드 모두 성공 |
|
||||
| 최신 unsigned Release MSIX | **15,228,096 bytes**, SHA-256 `3D7FC0C0837756C860AC660DBCEA30BF543D261FE0EEEAE1CDA294FA95CC3BF1` |
|
||||
| 패키지 감사 | 75항목, `.t2s`·실설정·라이선스·인증서·K3D vendor 파일 0 |
|
||||
| package-context DryRun | 최신 Release layout 실행·응답 확인, 인자 없음, 앱 소유 TCP 0, 중립 종료창 `매일경제TV`/`예`/`아니요` 확인 후 정상 종료 |
|
||||
|
||||
이 unsigned MSIX는 개발 package-context 실행 검증용이며 위 signed 배포 패키지를 대체하지
|
||||
않는다. 이 추가 회차에서도 scene/PREPARE/TAKE/NEXT/DB write와 Tornado 연결은 실행하지 않았다.
|
||||
|
||||
## 판정 경계
|
||||
|
||||
- 35/35 자동 장면 동등성은 모든 장면의 실제 PGM 출력 완료를 뜻하지 않는다.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
새 `DynamicK3dSession`도 같은 순서를 사용한다. output channel이 명시된 경우에만 `EndTransactionOnChannel`을 사용하며 layout `10`은 `Prepare`/`Play`/`CutOut`에만 전달한다. transaction 중 실패하면 `RollbackTransaction`을 시도하고 원래 실패를 보존한다.
|
||||
|
||||
공통 scene fade/background는 Web 입력이 아니라 로컬 trusted 설정이다. 원본 `ComboDi.SelectedIndex`에 맞춘 fade 기본값은 6이다. background kind와 scene root 아래 상대 asset, video loop를 `PlayoutSceneCompositionFactory`가 검사하며 `DryRun`도 실제 COM 전에 파일 존재, 확장자, root 탈출과 reparse point를 fail-closed 검증한다. 실제 asset 경로는 Web preview나 wire status에 노출하지 않는다.
|
||||
공통 scene background 경로는 Web 입력이 아니라 로컬 trusted 설정과 네이티브 파일 선택에서만 온다. fade는 원본 `DissolveTime`/`ComboDi.SelectedIndex`와 같은 0~19 폐쇄형 숫자 intent이며 기본 인덱스는 6(화면 표시 7)이다. K3D 호출도 원본의 raw Int32를 보존해 `SetSceneEffectType(10, FADE, index)`와 공통 `SetBackgroundVideo(path, 2004, 10)`을 사용하고, s5006 내부 배경의 `(1, 1)`과 섞지 않는다. background kind와 scene root 아래 상대 asset, video loop를 `PlayoutSceneCompositionFactory`가 검사하며 `DryRun`도 실제 COM 전에 파일 존재, 확장자, root 탈출과 reparse point를 fail-closed 검증한다. 실제 asset 경로는 Web preview나 wire status에 노출하지 않는다.
|
||||
|
||||
## PREPARE, TAKE IN, NEXT, TAKE OUT 상태 전이
|
||||
|
||||
@@ -41,6 +41,8 @@ timeout, dispatch 뒤 cancellation 또는 결과가 불명확한 COM 실패는 `
|
||||
|
||||
operator command를 시작할 때 timer를 먼저 멈춘다. TAKE IN과 playlist NEXT 성공 뒤 반복 refresh를 시작하고 Page NEXT 뒤에는 원본처럼 다시 시작하지 않는다. 새 runtime은 이전 tracked PLAY의 callback drain을 먼저 확인한 뒤 해당 cut의 첫 `m_time`, 이후 3초의 전체 cooldown을 시작한다. 원본의 UI timer보다 실제 cadence가 callback drain만큼 길어지는 의도적 안전 적응이며, callback 대기와 delay를 겹쳐 `playCompletionPending`이 사실상 계속 유지되는 상태를 방지한다.
|
||||
|
||||
단, 5076/5079/5080/5081의 `m_time=3000000`은 원본에서 `m_time * 1000` Int32가 overflow되어 WinForms `Timer.Interval` 설정이 예외로 끝나므로 자동 refresh를 시작하지 않는다. 이를 34.7일짜리 정상 타이머로 해석하지 않는다.
|
||||
|
||||
마이그레이션의 same-scene refresh는 retained on-air scene에 fresh DTO를 적용해 `BeginTransaction → mutation → QueryVariables → EndTransaction → Prepare(10) → tracked Play(10)`을 한 번 수행한다. 원본의 선행·후행 PLAY 두 번 중 선행 replay와 불완전한 `GetPlayingScene` proxy를 사용하지 않는 명시적 안전 차이다. 모든 PLAY를 callback accounting과 연결하고 중복 송출을 피하기 위한 결정이며, Round H의 정확한 `PLAY = 4`, `SCENE_PLAYED = 4` 예산으로 검증했다. refresh generation과 공개 상태는 atomic epoch로 묶어 이전 CTS의 늦은 delay, success, fault 또는 completion이 새 generation이나 stopped 상태를 덮어쓰지 못한다.
|
||||
|
||||
마지막 부분 page는 남은 row만 채우고 나머지 object를 clear/hide한다. `s5088` NXT 비교와 조회 index는 모두 `i + pageIndex * 12`를 사용한다. page 경계값과 partial-page clearing은 자동 테스트로 검증했다.
|
||||
@@ -74,6 +76,7 @@ vendor event handler의 `OnScenePlayed`, `OnCutOut`, `OnStopAll`은 managed call
|
||||
|
||||
- `OnScenePlayed` 성공 뒤에만 이전 retired scene을 unload/release한다.
|
||||
- pending Play callback이 있으면 TAKE OUT을 제외한 PREPARE/TAKE IN/NEXT/timer refresh를 fail-closed 차단한다.
|
||||
- pending CutOut/StopAll callback이 있으면 중복 TAKE OUT을 포함한 모든 scene 명령을 차단하고, 일치하는 완료 callback을 drain한 뒤에만 다음 PREPARE를 허용한다.
|
||||
- `CutOut`/`StopAll` dispatch 전에 completion counter를 올리고 동기 호출 실패 시 원복한다. 성공 callback은 대응 counter를 하나 줄이고, stop/cut으로 중단된 Play는 별도 `OnScenePlayed`가 없을 수 있으므로 pending Play accounting을 취소한다.
|
||||
- pending lifecycle callback, queue overflow, callback failure 또는 connection generation 불일치가 있으면 Disconnect와 조기 unload를 하지 않고 session을 abandon/quarantine한다.
|
||||
- 이전 generation의 늦은 callback은 현재 scene state를 변경하지 않는다.
|
||||
|
||||
@@ -86,10 +86,10 @@ fixed/industry/comparison 트리의 원본 double-click과 독립적으로 처
|
||||
| `tabControl1_SelectedIndexChanged` | 탭별 최초 데이터 조회 | 10개 identity별 C# activation gate; 최초 진입 1회 load, 재진입 snapshot 유지 |
|
||||
| 탭 drag 이벤트 | 10개 탭 표시 순서 교환 | dragover 대상 진입 시 identity와 예상 위치를 C#이 검증해 교환; 중복 전달은 no-op, active tab·playlist 불변 |
|
||||
| 검색 버튼/Enter | 종목 검색 | 원본 KOSPI→KOSDAQ→NXT 순서와 마지막 이름 일치 규칙 유지 |
|
||||
| 컷 click/double-click/drag | 원본 31컷 선택·추가 | plain/Ctrl/Shift/Ctrl+Shift와 원본 double-click 판정, batch 추가 |
|
||||
| playlist click/key/buttons/drag | 선택, enabled, 이동, 삭제, 다중 선택 행 이동 | 선택된 행은 원래 순서를 보존한 한 블록으로 임의 위치에 이동; C# playlist authority와 PREPARED/PROGRAM/pending/unknown mutation 차단 유지 |
|
||||
| 컷 click/double-click/key/drag | 원본 런타임 컷 선택·추가 | 최대 50 물리행, pointer double-click, Arrow/Home/End/Space와 Ctrl/Shift selection mark, batch 추가 |
|
||||
| playlist click/key/buttons/drag | F8 후보, 삭제 선택, enabled, 이동, 삭제, 단일 행 drag | F8 후보·PROGRAM·DEL 집합·Shift anchor를 분리하고 drag는 실제 source 한 행만 이동; 송출 mutation 차단 유지 |
|
||||
| F2/F3 | 배경 선택·없음 | trusted sibling background root만 허용; 누락·reparse·escape fail closed |
|
||||
| F8 | TAKE IN | idle이면 내부 PREPARE → TAKE IN을 한 번에 실행 |
|
||||
| F8 | 선택 후보 TAKE IN | IDLE/PREPARED/PROGRAM에서 그 시점의 후보를 새 PREPARE → TAKE IN으로 실행; PROGRAM·후보·DEL 집합·Shift anchor는 독립 |
|
||||
| NEXT | Page NEXT 또는 다음 행 | 같은 scene PageN 여부를 C#이 판정 |
|
||||
| Escape | TAKE OUT | modal이 없을 때만 StopAll 기반 TAKE OUT intent |
|
||||
| FormClosing | 종료 확인 | 기본 취소 확인창; 자동 TAKE OUT/반대 명령 없음 |
|
||||
@@ -99,9 +99,10 @@ fixed/industry/comparison 트리의 원본 double-click과 독립적으로 처
|
||||
FarPoint UI는 Web table/list로 표현한다. 다음 의미는 원본과 같이 유지한다.
|
||||
|
||||
- plain/Ctrl/Shift/Ctrl+Shift 선택과 현재 행
|
||||
- 첫 행은 자동 F8 후보지만 실제 클릭 전에는 DEL 선택이 아니며, F8/Home/End도 기존 DEL 집합과 Shift anchor를 덮어쓰지 않음
|
||||
- column header 정렬과 현재 표시 순서
|
||||
- 위/아래 이동, 선택/전체 삭제와 enabled 토글
|
||||
- playlist의 비연속 다중 선택을 한 블록으로 유지하는 행 drag와 임의 before/after drop
|
||||
- playlist의 실제 drag source 한 행을 이동하는 임의 before/after drop; 비연속 Ctrl/Shift 집합은 삭제 선택으로만 사용
|
||||
- UC6/EList 추천 행의 opaque `rowId` 기반 drag 재정렬
|
||||
- single-click과 double-click을 컨트롤별로 구분
|
||||
- 부모 section 전체 추가는 모든 자식을 먼저 materialize한 뒤 한 번에 commit
|
||||
@@ -117,7 +118,7 @@ Theme 결과 double-click은 원본의 5행 현재가 경로를 사용한다. PL
|
||||
|
||||
| 원본 동작 | Web 입력 | C# 권위와 중복 안전 |
|
||||
|---|---|---|
|
||||
| `PlayList_DragDrop/DragOver` | 내부 custom MIME와 opaque source/target `rowId`, before/after | source가 선택 행이면 현재 선택 전체를 표시 순서대로 이동한다. 체크박스에서 시작한 drag는 거부하고 PREPARED/PROGRAM/pending/unknown에서는 변경하지 않는다. |
|
||||
| FarPoint `AllowRowMove` | 내부 custom MIME와 opaque source/target `rowId`, before/after | 실제로 끌어 잡은 source 한 행만 이동한다. 수동 Ctrl/Shift 삭제 선택을 다중 행 drag로 확대하지 않으며, 체크박스에서 시작한 drag와 PREPARED/PROGRAM/pending/unknown 변경을 거부한다. |
|
||||
| `tab_DragOver/MouseDown/Move/Up` | drop을 기다리지 않고 dragover로 `hover-swap-tabs` 전송 | C# 탭 identity와 `expectedSourceIndex/expectedTargetIndex`가 현재 순서와 일치할 때만 교환하므로 같은 메시지의 중복 전달이 순서를 되돌리지 않는다. |
|
||||
| UC6 `FpSpread2.AllowDragDrop` | ThemeA/EList 편집행의 내부 custom MIME와 opaque `rowId` | 워크플로가 임의 before/after 위치를 계산한다. 존재하지 않는 ID, 같은 행, 추가 payload 필드는 strict bridge에서 거부하거나 no-op 처리한다. |
|
||||
|
||||
@@ -129,7 +130,7 @@ reorder와 cut append가 함께 발생하지 않는다.
|
||||
체크박스 origin도 브라우저 동작을 반영해 보강했다. Chromium의 `dragstart.target`은 실제
|
||||
pointer가 체크박스에서 시작했어도 draggable 조상 행일 수 있다. pointerdown 때 checkbox
|
||||
origin row를 기억해 이어지는 dragstart를 거부하고 pointerup/pointercancel/dragend에서 지운다.
|
||||
회귀 테스트는 이 target 형태, 다중선택 보존, row MIME의 조상 무시, 외부 text 거부와 exact
|
||||
회귀 테스트는 이 target 형태, 단일 source 행 이동, row MIME의 조상 무시, 외부 text 거부와 exact
|
||||
selected-cut token의 1회 처리를 DOM event 순서로 재현한다.
|
||||
|
||||
### ThemeA 이름 중복확인
|
||||
@@ -183,7 +184,9 @@ gate 때문에 `DryRun`으로 남았고 Tornado TCP 연결과 Network Monitoring
|
||||
|
||||
- `삼성` 검색 결과 **63개**, `삼성출판사` 선택, 선택한 컷 3개를 한 번의 drop으로
|
||||
playlist 3행에 추가
|
||||
- playlist multi-row block reorder와 enabled 체크박스 origin drag 차단
|
||||
- 당시 package의 playlist multi-row block reorder와 enabled 체크박스 origin drag 차단.
|
||||
block reorder는 이후 원본 재감사에서 source 단일 행 이동으로 교정됐으므로 현재 코드의
|
||||
단일 행 drag package 실측으로 간주하지 않음
|
||||
- mouse release 전에 dragover로 시장 탭 순서 교환
|
||||
- EList 추천행 **5개** drag reorder. 수량 입력칸에서 시작한 실제 마우스 drag는 순서를
|
||||
바꾸지 않고, 행의 비입력 영역에서 시작한 drag만 순서를 바꾸는 것을 확인
|
||||
@@ -203,9 +206,11 @@ Live 연결/해제 경로만 검증하며 scene/PGM 방송 송출 성공을 뜻
|
||||
| `s5077` | 6 | 6종목 테마 경로 |
|
||||
| `s5088` | 12 | 12종목 테마 경로 |
|
||||
|
||||
`pageCount = min(20, ceil(itemCount / pageSize))`를 사용한다. 0행, 정확한 경계,
|
||||
마지막 부분 페이지, 20페이지 cap, 21페이지 요청 거부, 같은 scene의 `GetPlayingScene`
|
||||
Page NEXT와 다음 enabled playlist 항목 NEXT를 자동 테스트로 비교한다.
|
||||
행을 만들 때 `pageCount = min(20, ceil(itemCount / pageSize))`를 사용하고, 송출을
|
||||
시작할 때 operator row의 `current/total` suffix를 해당 on-air cycle 동안 고정한다.
|
||||
column 3이 정확히 `5단 표그래프`, `6종목 현재가`, `12종목 현재가`인 행만 Page NEXT를
|
||||
허용한다. 0행, 정확한 경계, 마지막 부분 페이지, 20페이지 cap, 21페이지 요청 거부,
|
||||
retained on-air scene Page NEXT와 다음 enabled playlist 항목 NEXT를 자동 테스트로 비교한다.
|
||||
|
||||
## 개발 DB write 증적
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
- KOSPI `T_STOCK`, KOSDAQ `T_KOSDAQ_STOCK`, NXT KOSPI `N_STOCK/N_ONLINE`, NXT KOSDAQ `N_KOSDAQ_STOCK/N_KOSDAQ_ONLINE`을 매개변수 쿼리로 검색한다.
|
||||
- LIKE의 `%`, `_`, `!`는 escape하고 검색어·결과 수·열 스키마·종목 코드 형식을 제한한다.
|
||||
- 검색 결과는 시장, 종목명, 종목 코드를 함께 보존하므로 원본처럼 이름을 다시 훑어 코드를 추정하지 않는다.
|
||||
- 실제 실행 자산 `bin/Debug/Res/종목.ini`의 31개 항목을 원본 순서로 표시한다.
|
||||
- 실행 자산 SHA-256은 `45DFB1804828F0E4A45F73D681AF0709CE5662872FAA49CFC9F7A0B940409E20`으로 고정했다. 소스 `RES/종목.ini`와 다른 `시간외단일가` 표기는 실행 자산을 기준으로 한다.
|
||||
- MSIX에서도 수정 가능한 `%LOCALAPPDATA%/MBN_STOCK_WEBVIEW/Res/종목.ini`를 먼저 읽고, 외부 파일이 없을 때만 패키지 실행 폴더의 CP949 `Res/종목.ini`를 확인한다. 유효한 파일은 원본 물리 순서와 `-` 구분행을 그대로 표시한다.
|
||||
- 외부 파일이 존재하지만 잘못됐거나, 두 후보가 모두 없거나 유효하지 않으면 부분 목록을 사용하지 않는다. 실행 자산 SHA-256 `45DFB1804828F0E4A45F73D681AF0709CE5662872FAA49CFC9F7A0B940409E20`에서 고정한 31개 내장 목록으로 폴백하며 경로 없는 경고를 표시한다. 실제 파일은 Git/MSIX에 넣지 않는다.
|
||||
- NXT 종목은 원본과 같이 `1열판기본_현재가`와 `N5001`만 허용한다.
|
||||
- 5일선·20일선은 컷 추가 시점의 사본이 아니라 현재 전역 체크 상태를 모든 캔들 항목에 동기화하고 PREPARE 직전에 다시 고정한다.
|
||||
- 저장된 typed 종목 항목은 builder, alias, selection, 종목, 이동평균선이 모두 재생성 결과와 같을 때만 복원한다. 실패한 항목은 일반 catalog 경로로 우회하지 못한다.
|
||||
|
||||
@@ -95,10 +95,10 @@ SDK가 기본 위치에 없다면 x64 SDK의 `TlbImp.exe` 절대 경로를 `-Tlb
|
||||
| `sceneDirectory` | Test/Live에서 사용하는 외부 `.t2s` 루트의 절대 경로. `null`은 안전한 미설정 상태 |
|
||||
| `outputChannel` | 확인된 전용 출력 채널. `null`은 안전한 미설정 상태 |
|
||||
| `layoutIndex` | 씬 플레이어 layout 위치 |
|
||||
| `legacySceneFadeDuration` | 원본 `ComboDi.SelectedIndex`에 대응하는 fade. 기본값 6, 허용 범위 0~60 |
|
||||
| `legacySceneFadeDuration` | 원본 `ComboDi.SelectedIndex`에 대응하는 fade. 기본 인덱스 6(화면 7), 허용 범위 0~19(화면 표시 1~20) |
|
||||
| `legacySceneBackgroundKind` | trusted 공통 배경 `None`, `Texture`, `Video`; Web은 임의 경로를 보내지 않고 F2/F3가 native picker/toggle을 호출한다. 현재 picker는 `sceneDirectory` 내부만 허용하므로 원본의 sibling `배경` root와는 아직 동등하지 않음 |
|
||||
| `legacySceneBackgroundAssetPath` | `sceneDirectory` 아래의 상대 asset. 경로 탈출·reparse·누락 파일은 DryRun에서도 거부 |
|
||||
| `legacySceneBackgroundVideoLoopCount`, `legacySceneBackgroundVideoLoopInfinite` | 공통 video 배경의 bounded loop 설정 |
|
||||
| `legacySceneBackgroundVideoLoopCount`, `legacySceneBackgroundVideoLoopInfinite` | 공통 video 배경 설정. true는 원본 K3D raw Int32 `10`으로, false는 `0`으로 매핑하며 s5006의 raw `1`과 구분 |
|
||||
| `testProcessWindowTitlePattern` | 전용 테스트 인스턴스만 식별하는 창 제목 패턴 |
|
||||
| `testSceneAllowlist` | 실제 COM을 사용하는 `Test`와 `Live` 모두에서 허용한 scene name(code)의 폐쇄형 목록. 경로나 확장자는 넣지 않음(기존 설정 호환을 위해 이름 유지) |
|
||||
| `trustedLiveOutputEnabled` | 운영자가 로컬 파일에서만 설정하는 라이브 1차 게이트 |
|
||||
@@ -135,7 +135,7 @@ MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS
|
||||
MBN_STOCK_PLAYOUT_RECONNECT_ENABLED
|
||||
```
|
||||
|
||||
`testSceneAllowlist`, `trustedLiveOutputEnabled`, `maximumAutomaticRefreshesPerTakeIn`은 환경 변수로 변경할 수 없으며 로컬 설정 파일에서만 관리합니다. 이름은 기존 설정 호환을 위해 유지하지만 allowlist는 Test뿐 아니라 Live의 PREPARE, TAKE IN 재검사, NEXT와 timer refresh에도 적용되고 비어 있으면 실제 모드를 시작하지 않습니다. 자동 갱신 상한은 Web payload나 환경 변수로 늘릴 수 없고 COM dispatch 경계에서 다시 검사합니다. 공통 background/fade도 Web payload가 아니라 이 trusted 프로세스 설정 경계에서만 결정합니다. `PlayoutSceneCompositionFactory`는 첫 DryRun 또는 실제 PREPARE 전에 background asset의 상대 경로, 허용 확장자, 존재 여부와 reparse ancestry를 검사하며 실제 경로는 Web status/preview에 노출하지 않습니다. `SceneDirectory`는 Test/Live에서 존재하는 비-reparse 외부 디렉터리여야 하며, 엔진은 상대 `.t2s` 파일을 정규화해 이 루트 밖으로 나가는 경로를 거부합니다. scene file의 basename과 scene name 및 allowlist 항목도 서로 일치해야 합니다. 설정 파일은 실행 계정만 읽을 수 있도록 ACL을 제한합니다. 라이선스 키나 인증정보를 이 파일에 기록하지 않습니다.
|
||||
`testSceneAllowlist`, `trustedLiveOutputEnabled`, `maximumAutomaticRefreshesPerTakeIn`은 환경 변수로 변경할 수 없으며 로컬 설정 파일에서만 관리합니다. 이름은 기존 설정 호환을 위해 유지하지만 allowlist는 Test뿐 아니라 Live의 PREPARE, TAKE IN 재검사, NEXT와 timer refresh에도 적용되고 비어 있으면 실제 모드를 시작하지 않습니다. 자동 갱신 상한은 Web payload나 환경 변수로 늘릴 수 없고 COM dispatch 경계에서 다시 검사합니다. 공통 background는 Web 경로 payload가 아니라 trusted 프로세스 설정과 네이티브 파일 선택 경계에서만 결정합니다. fade는 원본 `DissolveTime` 20개 항목과 같은 폐쇄형 숫자 intent(0~19)로만 변경하며 기본값은 6입니다. `PlayoutSceneCompositionFactory`는 첫 DryRun 또는 실제 PREPARE 전에 background asset의 상대 경로, 허용 확장자, 존재 여부와 reparse ancestry를 검사하며 실제 경로는 Web status/preview에 노출하지 않습니다. `SceneDirectory`는 Test/Live에서 존재하는 비-reparse 외부 디렉터리여야 하며, 엔진은 상대 `.t2s` 파일을 정규화해 이 루트 밖으로 나가는 경로를 거부합니다. scene file의 basename과 scene name 및 allowlist 항목도 서로 일치해야 합니다. 설정 파일은 실행 계정만 읽을 수 있도록 ACL을 제한합니다. 라이선스 키나 인증정보를 이 파일에 기록하지 않습니다.
|
||||
|
||||
### KTAP 포트와 Network Monitoring 판정
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ PREPARE의 공통 순서는 다음과 같다.
|
||||
|
||||
TAKE IN은 PREPARE 때의 오래된 DTO를 그대로 재생하지 않는다. 원본 `ONAirMode`와 같이 현재 playlist snapshot의 같은 entry/page를 실제 DB에서 다시 조회하고 새 scene을 `LoadScene` → transaction → `Prepare(10)`한 뒤, 그 prepare가 명확히 성공한 경우에만 `Play(10)`을 한 번 호출한다. PREPARE를 다시 누르는 원본 toggle은 `TakeOut(All)`/`StopAll`로 state를 정리한다. timeout 또는 `OutcomeUnknown`은 retry 가능한 실패로 낮추지 않는다.
|
||||
|
||||
공통 fade와 배경은 Web 입력이 아니라 로컬 trusted playout 설정에서만 온다. `legacySceneFadeDuration`의 원본 기본값은 6이며, `legacySceneBackgroundKind`, scene root 아래의 상대 `legacySceneBackgroundAssetPath`, video loop 설정을 `PlayoutSceneCompositionFactory`가 검증한다. `DryRun`에서도 경로 탈출, 절대 경로, 허용되지 않은 확장자, reparse point, 누락 파일을 실제 COM 전에 fail-closed preflight한다. asset 경로는 Web 상태와 preview에 노출하지 않는다.
|
||||
공통 배경 경로는 Web 입력이 아니라 로컬 trusted playout 설정과 네이티브 파일 선택에서만 온다. `legacySceneFadeDuration`의 원본 기본 인덱스는 6(화면 표시 7)이고 화면의 `DissolveTime`은 선택 인덱스 0~19만 C#에 보낸다. 실제 K3D에는 원본 raw `SetSceneEffectType(10, FADE, index)` 및 공통 배경 `(path, 2004, 10)`을 전달하며 s5006의 `(path, 1, 1)`과 구분한다. `legacySceneBackgroundKind`, scene root 아래의 상대 `legacySceneBackgroundAssetPath`, video loop 설정을 `PlayoutSceneCompositionFactory`가 검증한다. `DryRun`에서도 경로 탈출, 절대 경로, 허용되지 않은 확장자, reparse point, 누락 파일을 실제 COM 전에 fail-closed preflight한다. asset 경로는 Web 상태와 preview에 노출하지 않는다.
|
||||
|
||||
이 순서는 `RegisteredLegacySceneCueProviderTests`, `LegacyPlayoutWorkflowTests`, Playout의 `DynamicK3dSessionTests`, `TornadoPlayoutEngineTests`에서 검증한다. 실제 COM 형식과 vendor 구현은 계속 `IPlayoutEngine` 뒤에 있다.
|
||||
|
||||
@@ -136,7 +136,7 @@ TAKE IN은 PREPARE 때의 오래된 DTO를 그대로 재생하지 않는다. 원
|
||||
- `s5088`의 NXT 인덱스는 비교와 조회 모두 `i + pageIndex * 12`를 사용한다.
|
||||
- 현재 항목에 다음 page가 있으면 operator Page NEXT는 playlist index를 유지하되 원본 `Next_Scene(0)`처럼 다음 page 데이터를 조회하고 새 scene을 `LoadScene` → transaction mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`한다. 이 경로는 `GetPlayingScene` in-place refresh가 아니다.
|
||||
- 마지막 page면 다음 활성 playlist 항목으로 이동한다. 비활성 항목은 원본처럼 앞으로 건너뛰며 끝에서 wrap하지 않는다.
|
||||
- 원본 timer는 TAKE IN과 playlist NEXT 뒤 반복되며 첫 `m_time` 이후 3초 주기로 계속 실행된다. 새 runtime도 반복 갱신을 유지하지만, 이전 tracked `Play`의 `OnScenePlayed` callback을 drain한 뒤에야 첫 `m_time` 또는 3초의 **전체 cooldown**을 시작한다. 이는 callback 대기와 delay가 겹쳐 operator window가 사라지는 일을 막는 의도적 안전 적응이다. 모든 operator command는 현재 epoch를 먼저 중단하고 Page NEXT 뒤에는 refresh를 재시작하지 않는다.
|
||||
- 원본 timer는 TAKE IN과 playlist NEXT 뒤 반복되며 첫 `m_time` 이후 3초 주기로 계속 실행된다. 새 runtime도 반복 갱신을 유지하지만, 이전 tracked `Play`의 `OnScenePlayed` callback을 drain한 뒤에야 첫 `m_time` 또는 3초의 **전체 cooldown**을 시작한다. 이는 callback 대기와 delay가 겹쳐 operator window가 사라지는 일을 막는 의도적 안전 적응이다. 모든 operator command는 현재 epoch를 먼저 중단하고 Page NEXT 뒤에는 refresh를 재시작하지 않는다. 단, 5076/5079/5080/5081은 원본의 `m_time * 1000` Int32 overflow 뒤 Timer 설정 예외로 실제 갱신이 시작되지 않으므로 명시적으로 비활성화한다.
|
||||
- 원본 `timer1_Tick`에는 mutation 전 선행 `Play(10)`과 `Prepare` 뒤 후행 `Play(10)`이 모두 있다. 마이그레이션은 불완전한 `GetPlayingScene` proxy 대신 retained on-air scene에 fresh DTO mutation을 적용하고 `BeginTransaction → mutation → QueryVariables → EndTransaction → Prepare(10) → tracked Play(10)` 한 번만 보낸다. 선행 replay를 생략한 것은 callback 추적 없이 PLAY를 중복시키지 않기 위한 명시적 안전 차이이며 Round H의 정확한 PLAY/callback 4회 예산에 반영됐다.
|
||||
- refresh CTS와 공개 상태는 atomic epoch로 결합한다. `Replace`/`Stop`/조건부 update/completion이 같은 lock에서 generation을 확인하므로 취소된 이전 loop가 `active`, `NextAt`, success 또는 fault를 새 loop나 중단 상태 위에 다시 쓰지 못한다.
|
||||
|
||||
@@ -155,6 +155,7 @@ TAKE IN은 PREPARE 때의 오래된 DTO를 그대로 재생하지 않는다. 원
|
||||
- `OnCutOut`은 해당 layout의 on-air 참조를, `OnStopAll`은 player의 모든 on-air 참조를 정리할 근거다.
|
||||
- 이전 connection generation의 늦은 callback은 현재 state를 바꾸지 않는다.
|
||||
- pending Play callback이 있으면 TAKE OUT을 제외한 PREPARE/TAKE IN/NEXT/timer refresh를 fail-closed 차단한다. 어떤 lifecycle callback이든 pending이면 Disconnect와 session recycle을 하지 않고 abandon/quarantine 및 `OutcomeUnknown`으로 보수적으로 전환한다.
|
||||
- pending CutOut/StopAll callback이 있으면 반복 TAKE OUT을 포함한 모든 scene 명령을 SDK 전에 차단하며, 일치하는 완료 callback 뒤에만 다음 PREPARE를 허용한다.
|
||||
- `StopAll`/`CutOut`은 SDK dispatch 전에 각 pending completion counter를 올리고 동기 호출 실패 시 되돌린다. 성공 callback은 대응 counter를 정확히 하나 줄이며, 성공한 stop/cut은 중단된 Play가 뒤늦게 `OnScenePlayed`를 보내지 않을 수 있으므로 해당 pending Play accounting을 취소한다. dispatch 뒤 cancellation/timeout은 결과 불명확으로 취급한다.
|
||||
- 장기 실행 시 retired scene은 callback으로 안전성이 확인된 뒤 unload/release한다.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
## 원본 동작
|
||||
|
||||
- `T2S_FOLDER_PATH`는 실행 폴더의 `Cuts\`이다.
|
||||
- MainForm 시작 시에는 `Cuts\video\기본.vrv`를 기본 배경으로 지정한다.
|
||||
- F2/`btnback_Click`는 먼저 `Cuts\..\배경\기본.vrv`를 기본값으로 두고,
|
||||
`배경` 폴더에서 vrv/jpg/png 파일 선택 창을 연다.
|
||||
- F3/`ckback_CheckedChanged`가 배경을 끄면 파일 값을 비우고, 다시 켜면
|
||||
@@ -14,9 +15,10 @@
|
||||
|
||||
## 새 구현
|
||||
|
||||
`SceneDirectory`(Cuts)는 기존 장면과 장면 내부 자산만 계속 담당한다. 운영자
|
||||
F2/F3 배경은 별도 `LegacyBackgroundDirectory`에서만 해석하며 두 allowlist
|
||||
root를 합치거나 서로 대신 사용하지 않는다.
|
||||
`SceneDirectory`(Cuts)는 기존 장면, 장면 내부 자산과 시작 시 기본 배경
|
||||
`video\기본.vrv`를 담당한다. 운영자 F2/F3 배경은 별도
|
||||
`LegacyBackgroundDirectory`에서만 해석하며 두 allowlist root를 합치거나 서로
|
||||
대신 사용하지 않는다.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -37,6 +39,11 @@ F2/F3는 IDLE에서만 변경할 수 있고 변경은 다음 PREPARE부터 적
|
||||
통과하지 못하면 재생 가능한 mutation으로 만들지 않는다. F3로 다시 켤 때도
|
||||
이전 사용자 선택 파일이 아니라 원본과 같은 `기본.vrv`를 새로 검증한다.
|
||||
|
||||
명시적인 로컬 배경 설정이 없을 때의 시작 폴백은 오직
|
||||
`SceneDirectory\video\기본.vrv`만 검사한다. 이 파일이 없거나 안전 검증에
|
||||
실패해도 sibling `배경\기본.vrv`로 대신하지 않고, 경로를 노출하지 않는 경고와
|
||||
함께 배경 없음으로 시작한다. 반대로 F2/F3는 시작 폴백 파일을 대신 사용하지 않는다.
|
||||
|
||||
## Fail-closed 검증
|
||||
|
||||
배경 root와 파일은 사용할 때마다 다음 조건을 모두 만족해야 한다.
|
||||
@@ -49,11 +56,11 @@ F2/F3는 IDLE에서만 변경할 수 있고 변경은 다음 PREPARE부터 적
|
||||
- 검사 중 디렉터리 조상과 파일 handle은 delete/rename 공유를 허용하지 않음
|
||||
|
||||
root나 파일이 없거나 검증 결과가 불명확하면 경로를 Web/오류에 노출하지 않고
|
||||
F2/F3 또는 PREPARE를 거부한다. Cuts root로의 fallback은 없다.
|
||||
F2/F3 또는 PREPARE를 거부한다. 시작 경로와 F2/F3 경로 사이의 fallback은 없다.
|
||||
|
||||
## 현재 자산 확인
|
||||
|
||||
2026-07-12에 원본 `bin\Debug\Cuts`의 형제 `bin\Debug\배경`과
|
||||
`배경\기본.vrv`를 확인했으나 둘 다 존재하지 않았다. 따라서 자산이 배치되기
|
||||
전까지 현재 환경의 기본 배경은 의도대로 fail closed 상태다. 실제 vrv 파일은
|
||||
Git/MSIX에 포함하지 않는다.
|
||||
2026-07-15에 원본 `bin\Debug\Cuts\video\기본.vrv`와 Cuts의 형제
|
||||
`bin\Debug\배경\기본.vrv`를 확인했으나 둘 다 존재하지 않았다. 따라서 자산이
|
||||
배치되기 전까지 현재 환경의 기본 배경은 의도대로 fail closed 상태다. 실제 vrv
|
||||
파일은 Git/MSIX에 포함하지 않는다.
|
||||
|
||||
38
docs/UC1_TREE_EVENT_PARITY.md
Normal file
38
docs/UC1_TREE_EVENT_PARITY.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# UC1 tree event parity
|
||||
|
||||
Source evidence is the read-only original
|
||||
`C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\Control\UC1.cs`:
|
||||
|
||||
- `UC1_Load` checks `Expand`, selects the first rendered root, and calls
|
||||
`EnsureVisible`.
|
||||
- `treeView_MouseDown`, `treeView_BeforeExpand`, and
|
||||
`treeView_BeforeCollapse` prevent a double-click from also changing a
|
||||
root's expanded state.
|
||||
- `treeView_DoubleClick` activates either the selected leaf or every direct
|
||||
child of the selected root.
|
||||
- `chE_CheckedChanged` expands or collapses every root. Collapsing does not
|
||||
clear `SelectedNode`.
|
||||
|
||||
The WebView tree therefore owns only presentation state (open roots, selected
|
||||
node, focused node, and scroll position). C# remains the authority for every
|
||||
leaf/root action. A root label click selects without expanding; only its
|
||||
disclosure glyph changes `open`. A root or leaf double-click emits one closed
|
||||
native intent and does not invoke the browser's whole-`summary` toggle.
|
||||
|
||||
On each real tab activation/re-entry the presentation state is recreated like
|
||||
the original `UC1`: `Expand` is checked, all sections are open, and the first
|
||||
root is selected, focused, and scrolled into view. State-only renders within
|
||||
the same activation preserve per-section open state, selection, focus, and
|
||||
scroll position. Busy and fixed-section-batch states reject both actions and
|
||||
local expand/collapse changes.
|
||||
|
||||
## Intentional defect exclusion
|
||||
|
||||
The original `treeView_DoubleClick` reads `uc_3.FpSpread` in the parent-node
|
||||
path before checking which UC1 file is active. For UC1 instances constructed
|
||||
with `uc_3 == null`, that can throw `NullReferenceException`; the surrounding
|
||||
empty `catch` silently drops the operator action. This is an original defect,
|
||||
not observable intended behavior, so the migration does **not** reproduce it.
|
||||
The WebView sends only the selected root identity and C# validates/materializes
|
||||
the corresponding section without dereferencing an unrelated comparison
|
||||
control.
|
||||
Reference in New Issue
Block a user