# 원본 WinForms Designer 이벤트 동등성 감사

감사 기준일은 2026-07-15이다. 원본 `C:\Users\MD\source\repos\MBN_STOCK_N`의 커밋
`309286309eaf20b58172247263f575753a860700`을 읽기 전용으로 조사했다. 대상은 실제로
Designer가 구독하는 이벤트이며, 선언만 있고 구독되지 않은 메서드는 수에 넣지 않았다.

Designer 구독은 186개이고, 같은 핸들러를 여러 컨트롤에 연결한 구독을 화면별로 합치면
171개다. 이 문서의 상세 목록은 그 171개를 한 번씩 모두 분류한다.

원본 `timerAliveChecker`는 Designer 구독이 아니다. `MainForm` 생성자 코드가 10초 interval의
WinForms Timer를 만들고 `timerAliveChecker_Tick`을 동적으로 구독한다. 따라서 186/171 집계에
억지로 더하지 않고 별도 활성 이벤트로 감사한다. 이 보충 경로도 현재 LegacyParityApp에
연결됐으므로, 현행 판정에는 미이관 Designer 이벤트나 미이관 동적 활성 이벤트가 없다.

## 분류 기준

- `IMPL`: 원본의 활성 업무 동작이 C# 소유 상태/워크플로, 엄격한 WebView 브리지 intent,
  또는 네이티브 playout 경로로 이관됐다.
- `DEAD`: 원본 메서드가 비어 있거나 전부 주석이거나 즉시 반환한다. 새 명령을 만들지 않는
  것이 원본과 같다.
- `VISUAL`: WinForms 전용 paint/draw/focus/resize 동작이며 DOM, CSS, 브라우저 기본 동작으로
  대체됐다.
- `DRAG`: 원본 drag/mouse 순서를 Web drag-and-drop과 C# 소유 순서 변경으로 이관한 묶음이다.
- `SAFETY`: 원본과 의도적으로 다르다. 기존 송출 안전 정책 때문에 자동 `StopAll`을 넣지 않는다.

## 집계

| 화면 | Designer 구독 | 고유 핸들러 | IMPL | DEAD | VISUAL | DRAG | SAFETY |
|---|---:|---:|---:|---:|---:|---:|---:|
| UC1 | 9 | 9 | 3 | 2 | 4 | 0 | 0 |
| UC2 | 4 | 4 | 3 | 1 | 0 | 0 | 0 |
| UC3 | 18 | 18 | 14 | 4 | 0 | 0 | 0 |
| UC4 | 15 | 15 | 13 | 2 | 0 | 0 | 0 |
| UC5 | 20 | 9 | 6 | 3 | 0 | 0 | 0 |
| UC6 | 13 | 13 | 12 | 1 | 0 | 0 | 0 |
| UC7 | 7 | 6 | 5 | 1 | 0 | 0 | 0 |
| AList | 3 | 3 | 3 | 0 | 0 | 0 | 0 |
| EList | 3 | 3 | 3 | 0 | 0 | 0 | 0 |
| FSell | 3 | 3 | 3 | 0 | 0 | 0 | 0 |
| GraphE | 15 | 15 | 14 | 1 | 0 | 0 | 0 |
| PList | 7 | 7 | 6 | 1 | 0 | 0 | 0 |
| ThemeA | 13 | 13 | 12 | 1 | 0 | 0 | 0 |
| VIList | 10 | 10 | 10 | 0 | 0 | 0 | 0 |
| MainForm | 46 | 43 | 24 | 3 | 6 | 9 | 1 |
| **합계** | **186** | **171** | **131** | **20** | **10** | **9** | **1** |

UC5는 20개 구독이 9개 핸들러를, UC7은 7개 구독이 6개 핸들러를, MainForm은
46개 구독이 43개 핸들러를 공유한다. 따라서 구독 수와 고유 핸들러 수가 다르다.

## 171개 핸들러 상세 증거

`@숫자`는 각 원본 `*.Designer.cs`의 이벤트 구독 줄이다.

### UC1 — `Control/UC1.Designer.cs`

- `IMPL (3)`: `treeView_DoubleClick@51`, `chE_CheckedChanged@73`, `UC1_Load@84`
- `DEAD (2)`: `treeView_BeforeSelect@49`, `treeView_AfterSelect@50`
- `VISUAL (4)`: `treeView_BeforeCollapse@46`, `treeView_BeforeExpand@47`,
  `treeView_DrawNode@48`, `treeView_MouseDown@52`

### UC2 — `Control/UC2.Designer.cs`

- `IMPL (3)`: `btnswap_Click@75`, `listbox_DoubleClick@106`, `UC2_Load@126`
- `DEAD (1)`: `listbox_SelectedIndexChanged@105`

### UC3 — `Control/UC3.Designer.cs`

- `IMPL (14)`: `btnswap_Click@154`, `txtSearch_KeyPress@220`, `listBox_DoubleClick@232`,
  `btnsearch_Click@249`, `txtj_KeyPress@314`, `btnsearch2_Click@328`,
  `FpSpread2_CellDoubleClick@396`, `btnAdd_Click@470`, `btn4_Click@531`,
  `btn3_Click@545`, `btn2_Click@559`, `btn1_Click@573`, `listBox1_DoubleClick@720`,
  `UC3_Load@741`
- `DEAD (4)`: `txtSearch_KeyDown@219`, `listBox_SelectedIndexChanged@231`,
  `FpSpread2_CellClick_1@395`, `FpSpread_CellClick@641`

### UC4 — `Control/UC4.Designer.cs`

- `IMPL (13)`: `btn1_Click@288`, `txts_KeyDown@297`, `btn2_Click@311`,
  `btn3_Click@325`, `btn4_Click@339`, `FpSpread1_CellClick@406`,
  `FpSpread1_CellDoubleClick@407`, `btnpre_Click@483`, `btnnow_Click@497`,
  `timer1_Tick@659`, `btnnow_6_Click@673`, `btnnow_12_Click@687`, `UC4_Load@752`
- `DEAD (2)`: `FpSpread1_DoubleClick@408`, `FpSpread2_CellClick@564`

### UC5 — `Control/UC5.Designer.cs`

- `IMPL (6)`: `btn1_Click@497`, `btnsearch2_Click@571`, `txtj_KeyPress@581`,
  `btnsearch1_Click@747`, `txts_KeyPress@757`, `UC5_Load@1007`
- `DEAD (3)`: `txtj_TextChanged@580`, `txts_TextChanged@756`, `FpSpread_CellClick@939`

### UC6 — `Control/UC6.Designer.cs`

- `IMPL (12)`: `btlistadd_Click@263`, `txtSearch_KeyPress@284`,
  `listBox_DoubleClick@295`, `btnsearch_Click@312`, `btnsave_Click@365`,
  `btndel_Click@379`, `FpSpread1_CellClick@456`, `FpSpread2_CellClick@570`,
  `FpSpread2_KeyDown@571`, `btnDelete_Click@648`, `btnAdd_Click@662`, `UC6_Load@673`
- `DEAD (1)`: `tabp6_1_Paint@331`
- 이벤트 구독과 별도로 원본 `FpSpread2.AllowDragDrop = true`가
  `Control/UC6.Designer.cs:519`에 있다. 이는 `DRAG` 경로의 전문가 추천행 재정렬로 이관한다.

### UC7 — `Control/UC7.Designer.cs`

- `IMPL (5)`: `btnsearch_Click@185`, `txts_KeyPress@194`, `btn4_Click@219`,
  `FpSpread1_CellClick@301`, `UC7_Load@369`
- `DEAD (1)`: `groupBox3_Enter@161`

### AList — `Form/AList.Designer.cs`

- `IMPL (3)`: `btnok_Click@64`, `btncn_Click@88`, `AList_Load@124`

### EList — `Form/EList.Designer.cs`

- `IMPL (3)`: `btncn_Click@56`, `btnok_Click@82`, `EList_Load@137`

### FSell — `Form/FSell.Designer.cs`

- `IMPL (3)`: `btncn_Click@109`, `btnok_Click@125`, `FSell_Load@337`

### GraphE — `Form/GraphE.Designer.cs`

- `IMPL (14)`: `upSearch_Click@220`, `txt1_KeyPress@229`, `downSearch_Click@244`,
  `FpSpread1_CellClick@312`, `FpSpread1_CellDoubleClick@313`, `btndel_Click@402`,
  `btnall_Click@417`, `btnAdd_Click@432`, `txtSearch_KeyPress@453`,
  `listBox_DoubleClick@464`, `btnsearch_Click@481`, `btadd_Click@531`,
  `btnok_Click@1274`, `GraphE_Load@1318`
- `DEAD (1)`: `GraphE_KeyPress@1319`

### PList — `Form/PList.Designer.cs`

- `IMPL (6)`: `btnadd_Click@55`, `listBox_DoubleClick@79`, `btncn_Click@95`,
  `button3_Click@111`, `button1_Click@127`, `PList_Load@139`
- `DEAD (1)`: `listBox_SelectedIndexChanged@78`

### ThemeA — `Form/ThemeA.Designer.cs`

- `IMPL (12)`: `btncancel_Click@246`, `btnok_Click@260`, `FpSpread_KeyDown@353`,
  `btn4_Click@422`, `btn3_Click@436`, `btn2_Click@450`, `btn1_Click@464`,
  `btnC_Click@479`, `listBox_DoubleClick@521`, `txtSearch_KeyPress@530`,
  `btnsearch_Click@547`, `Theme_Load@569`
- `DEAD (1)`: `listBox_SelectedIndexChanged@520`

### VIList — `Form/VIList.Designer.cs`

- `IMPL (10)`: `txtSearch_KeyPress@188`, `btnok_Click@202`, `btn4_Click@391`,
  `btn3_Click@405`, `btn2_Click@419`, `btn1_Click@433`, `listBox_DoubleClick@456`,
  `btnsearch_Click@473`, `btncancel_Click@487`, `VILIST_Load@528`

### MainForm — `MainForm.Designer.cs`

- `IMPL (24)`: `tabControl1_SelectedIndexChanged@1062`, `btnPrepare_Click@1465`,
  `PlayList_CellClick@1723`, `PlayList_KeyPress@1726`, `ckback_CheckedChanged@1818`,
  `btnback_Click@1854`, `btntdelete_Click@1869`, `btndelete_Click@1884`,
  `btnload_Click@1899`, `button5_Click@1914`, `btndown_Click@1930`, `btnup_Click@1946`,
  `checkBox1_CheckedChanged@1986`, `btnTake_Click@2018`, `txtSearch_KeyPress@2047`,
  `b1_Click@2108`, `button9_Click@2207`, `btnNext_Click@2410`,
  `btnTakeout_Click@2428`, `timer1_Tick@2433`, `timer3_Tick@2443`,
  `Form1_Load@2470`, `MainForm_KeyDown@2472`, `MainForm_KeyUp@2473`
- `DEAD (3)`: `PlayList_LeaveCell@1722`, `GridView1_RowLeave@2081`,
  `FpSpread2_CellDoubleClick@2280`
- `VISUAL (6)`: `panel6_Paint@1426`, `GridView1_CellEnter@2080`,
  `listBox_DrawItem@2189`, `timer2_Tick@2438`, `MainForm_Paint@2471`,
  `MainForm_Resize@2474`
- `DRAG (9)`: `tab_DragOver@1063`, `tab_MouseDown@1064`, `tab_MouseMove@1065`,
  `tab_MouseUp@1066`, `PlayList_DragDrop@1724`, `PlayList_DragOver@1725`,
  `listView1_MouseDown@2349`, `listView1_MouseMove@2350`, `listView1_MouseUp@2351`
- `SAFETY (1)`: `MainForm_FormClosing@2469`

원본 PROGRAM 중 enabled 변경에는 입력 경로별 차이가 있다. `PlayList_CellClick`은 `m_TakeIn`이면
checkbox 클릭을 거부하지만, `PlayList_KeyPress`의 Space와 `checkBox1_CheckedChanged`의 전체
변경은 같은 guard가 없어 다음 송출 행을 건너뛰는 데 쓰일 수 있다. 새 앱은 PROGRAM 중 이미
진행한 prefix와 현재 on-air 행을 고정하고 **미래 행만** Space/전체 enabled 변경을 허용한다.
원본의 운영 편의는 보존하면서 재생 이력과 현재 행을 바꾸지 않는 안전 적응이며 자동 계약 검증은
완료됐다. 이 변경을 포함한 최신 등록 AppX에서도 실제 키보드 입력으로 PROGRAM 중 현재 행 고정과
미래 행 enabled 변경을 최종 재검증했다.

## Designer 밖 동적 활성 이벤트

- 원본 `MainForm.cs:82-89`의 `timerAliveChecker`는 10초마다 DB 연결 끊김·복구를 확인하고
  상태 전이 때 로그를 남긴다. Designer 파일에 없다는 이유로 이전 186/171 집계에서 제외된
  것이며, 비활성 코드가 아니다.
- 현재 `MainWindow.xaml.cs`는 창 초기화 뒤 `StartDatabaseHealthMonitor()`를 호출한다.
  `MainWindow.DatabaseHealth.cs`는 같은 10초 첫 지연/주기를 사용해 Oracle/MariaDB를 점검하고
  전이 때만 native operator log를 남긴다.
- 운영자 DB 입력·송출·자동 갱신이 시작되면 active health check를 취소하고 공용 DB activity
  gate를 양보하며, 앱 종료 때 lifetime token과 active check를 모두 취소한다.
- 실제 DB 서버를 강제로 끊는 장애 주입은 하지 않았다. 수명주기·전이 중복 억제·priority gate·
  cancellation은 자동 회귀로 고정한다.

## ThemeA `중복확인` 동등성

원본 `Form/ThemeA.cs:84-109`는 입력 이름과 현재 KRX/NXT 선택을 기준으로 정확히 같은
테마명을 조회하고 `이미 사용중인 테마명입니다.` 또는 `사용 가능한 테마명입니다.`를 표시한다.
새 구현은 다음 경로를 사용한다.

1. `Web/index.html`의 `operator-catalog-name-check`가 원본 `중복확인` 버튼이다.
2. Web은 이름만 담은 `check-operator-catalog-theme-name` intent를 보낸다. market, code,
   table 이름 같은 DB 권한 정보는 보내지 못한다.
3. `LegacyBridgeProtocol`이 닫힌 payload와 128자 canonical text를 검증한다.
4. `LegacyOperatorController.CheckOperatorCatalogThemeNameAsync`가
   `LegacyOperatorCatalogWorkflowController.CheckThemeNameAvailabilityAsync`를 호출한다.
5. 워크플로는 C#이 소유한 현재 KRX/NXT 대상과 exact title을
   `IThemeCatalogPersistenceService.ThemeNameExistsAsync`에 전달한다. 구현은 선택한 provider의
   `SB_LIST`에 parameterized `COUNT(*)` SELECT를 정확히 한 번 실행하며 mutation executor를
   호출하지 않는다. 128자 테마명도 잘림 없는 exact query로 처리한다.
6. 조회 결과를 확정할 수 없으면 `Indeterminate`로 표시하며 DB 쓰기를 하지 않는다.

원본 쿼리는 edit 중인 현재 `SB_CODE`를 제외하지 않는다. 따라서 제목을 바꾸지 않은 기존
테마에서 `중복확인`을 누르면 자기 행 때문에 `이미 사용중인 테마명입니다.`가 표시된다. 새 구현도
고객의 원본 우선 요구에 따라 KRX/NXT 모두 이 동작을 그대로 보존한다.

검증은 `LegacyThemeCatalogPersistenceServiceTests`, `LegacyOperatorCatalogWorkflowTests`,
`LegacyOperatorCatalogBridgeTests`, `LegacyOperatorCatalogParityWebTests`에 있다.
KRX와 NXT의 같은 이름을 서로 혼동하지 않는지,
create의 중복/사용 가능, edit의 자기 행 중복, 128자 parameter binding, 결과 불명 상태,
엄격한 payload, Web 버튼 및 native dispatch를 확인한다.

## 의도적인 종료 안전 차이

원본 `MainForm.cs:751-780`은 종료 확인 뒤 `KAScenePlayer.StopAll()`, `KAScene.Unload()`,
500ms 대기, 현재 프로세스 강제 종료를 수행한다. 새 앱은 이를 그대로 이관하지 않는다.

- `MainWindow.CloseConfirmation.cs:88-119`는 PREPARED/PROGRAM, pending callback,
  `OutcomeUnknown`을 사용자에게 명시하고 종료 과정에서 TAKE OUT을 자동 실행하지 않는다.
- `MainWindow.Playout.cs:778-823`은 on-air 장면, pending callback, quarantine,
  `OutcomeUnknown`이 없을 때만 bounded disconnect를 시도한다.
- 이 차이는 기존 안전 게이트의 `결과 불명 시 반복 금지`와 `명시적 송출 명령` 정책을 지키기 위한
  활성 안전 요구사항이다. 동등성 작업에서 완화하거나 자동 `StopAll`로 바꾸지 않는다.

## 기타 감사 메모

- 원본 `MainForm.Designer.cs:1399`에서 PREPARE가 들어 있는 `panel3.Visible = false`다.
  새 Web UI도 PREPARE를 일반 조작 화면에서는 숨기고 네이티브/승인된 검증 경로는 유지한다.
- 원본의 단일 인스턴스 기대는 `LegacyParityApp/App.xaml.cs`의
  `AppInstance.FindOrRegisterForKey`로 구현됐다.
- 원본 `timer3_Tick`의 Tornado 상태 감시는 `IPlayoutEngine` 뒤의
  `TornadoPlayoutEngine` monitor/reconnect 상태 기계로 격리돼 있다.
- 이벤트 이름이 여러 화면에서 같더라도 화면별 메서드는 별개로 셌다. 예를 들어
  `btn1_Click`, `btnsearch_Click`, `listBox_DoubleClick`은 전역 이름 하나가 아니라 각 화면의
  서로 다른 바인딩이다.
