feat: restore legacy modal and drag behavior

This commit is contained in:
2026-07-16 01:33:48 +09:00
parent 83398044c6
commit d7ec571343
30 changed files with 3597 additions and 321 deletions

View File

@@ -182,19 +182,20 @@
고정한 계약:
- modal에서 처리한 Escape/F2/F3/F8은 뒤의 playout key listener로 전달하지 않는다. 이름 있는
playlist modal의 Escape는 modal만 닫고 TAKE OUT을 보내지 않는다.
- 원본 child form에는 `CancelButton`/`KeyPreview`/Escape handler가 없고 `ShowDialog`가 MainForm을
비활성화한다. 따라서 modal의 Escape는 form을 닫지도 TAKE OUT을 보내지으며,
F2/F3/F8과 playlist 전역 키도 owner로 전달하지 않는다.
- 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이 전역 선택을 덮어쓰던 경로는 제거했다.
- 원본 `DissolveTime`을 소유한 `panel3`은 Designer에서 항상 숨겨져 있으므로 WebView selector도
hidden/disabled로 유지하고 운영자 변경 intent를 노출하지 않는다. C#의 기존 0..19 인덱스와
기본 6은 scene effect 상태로만 보존한다.
## Slice 6: 탭 재진입과 dragover 교환
상태: 코드 및 자동 테스트 완료, 실제 DB 쓰기/Tornado 명령은 실행하지 않음.
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado 명령은 실행하지 않음.
원본 근거:
@@ -278,11 +279,61 @@
- PREPARED/PROGRAM/pending/unknown 또는 종목 미선택 상태에서는 컷 선택과 focus 이동을 함께
차단해 DOM과 C# 상태가 어긋나지 않게 한다.
## Slice 10: modal 소유권·PList 완료 순서와 컷 drag 임계값
상태: 코드·자동 테스트·unsigned Release package-context 시작/정상 종료 완료. 실제 컷 drag
마우스 실측과 DB 쓰기/Tornado 명령은 실행하지 않음.
원본 근거:
- `Form/PList.cs:173-196`, `222-237` 행 double-click과 확인 버튼 save/load/close 순서
- `Form/PList.cs:184`, `226`, `229``저장하겠습니까?`, `저장되었습니다`
- `Form/*Designer.cs`의 child form에 없는 `CancelButton`, `KeyPreview`, Escape handler
- `MainForm.cs:3427-3459`, `3534-3549`, `3635-3643` 컷 mouse-down rectangle,
drag 시작과 체크 행 physical-order drop
- `MainForm.Designer.cs:1391-1399` 숨겨진 `panel3`
고정한 계약:
- named playlist, GraphE, FSell/VI, ThemeA/EList modal은 opener를 기억하고 최초 legacy control에
focus하며 Tab/Shift+Tab을 modal 안에 가둔다. 중첩 alert를 닫으면 child modal의 이전 control로,
child modal을 닫으면 owner control로 focus를 돌려준다.
- child modal의 Escape는 원본처럼 no-op으로 소비하고 MainForm TAKE OUT으로 새지 않는다.
- PList save는 정확히 `저장하겠습니까?`를 묻는다. 확인 버튼 save 성공만
`저장되었습니다`를 표시하고, 행 double-click save는 성공 알림 없이 닫는다. No를 선택해도
원본의 unconditional `Close()`처럼 modal을 닫는다.
- 비동기 DB 명령 중에는 원본 동기 `ShowDialog` 호출이 반환되지 않던 구간처럼 close/cancel과
중복 명령을 잠근다. 정확한 command/requestId/definition/sequence receipt만 modal을 닫으며
실패 또는 결과 불명확 상태에서는 성공 알림·추정 close를 하지 않는 안전 교정을 유지한다.
- `PList.Get_FileName_Folder`가 새 `DataSource`를 지정할 때 non-empty ListBox의 첫 행이 자동
선택되는 동작을 열기·새로고침·추가·삭제 뒤 모두 유지한다. 다른 행 click은 즉시 시각 선택을
바꾸되 exact native selection receipt 전에는 확인·삭제를 잠그고, double-click은 같은 opaque
definition id에 묶어 receipt 뒤 한 번만 실행하므로 이전 첫 행을 잘못 저장/삭제하지 않는다.
- DB commit이 확정되고 read-back만 실패한 `CommittedOptimistic`은 원본처럼 저장 완료로
처리하되 warning은 유지한다. 반대로 이전 성공 outcome이 남은 채 현재 입력 검증이 실패한
경우에는 named workflow revision이 증가하지 않으므로 성공 receipt·알림·close를 만들지 않는다.
- category tree 검색 input을 state render가 다시 만들더라도 id, caret/selection 범위와 focus를
복원해 Enter 검색 뒤 원본 textbox의 연속 입력 동작을 유지한다.
- 컷 drag rectangle은 `Point(x-SM_CXDRAG, y-SM_CYDRAG)`와 Windows drag size를 사용하고
left/top 포함, right/bottom 제외 `Rectangle.Contains` 판정을 그대로 적용한다. 브라우저 native
drag 임계값은 사용하지 않으며 pointer capture 뒤 playlist 경계 안에서 놓았을 때만 기존
`DropSelectedCutsOnPlaylist`를 한 번 호출한다.
- `SM_CXDRAG/SM_CYDRAG`는 DPI 비인지 `GetSystemMetrics` 값을 그대로 쓰지 않는다.
`GetSystemMetricsForDpi(..., 96)`로 XAML host DIP 값을 읽고 `XamlRoot.RasterizationScale`
state에 싣는다. Web은 이를 `window.devicePixelRatio`와 대조해 과거 profile에 남은 browser
zoom까지 제거한 pointer-event CSS pixel 임계값을 계산한다. 100%/150% DPI와 retained zoom을
자동 테스트하며 비정상 scale 비율은 1로 fail-safe한다.
- `WM_SETTINGCHANGE`/`WM_DISPLAYCHANGE`/`WM_DPICHANGED`와 모든 state 게시 경계에서 값을 다시
읽어 시작 시 한 번만 캐시하지 않는다. cut-pointer-down 회신이 도착했을 때 아직 시작되지
않은 pointer gesture의 rectangle도 최신 값으로 다시 만든다. 이후 browser/pinch zoom 입력은
비활성화하고 API 실패나 0 이하는 축별 4 DIP로 fail-safe한다. 실제 drop 내용은 payload가
아니라 C#의 현재 선택 집합과 physical 순서가 계속 권위본이다.
## 다음 대조 순서
1. 컷 ListView의 drag 시작 임계값과 실제 package 키보드 회귀
2. GraphE/FSell/VI/PList/AList/ThemeA/EList modal별 남은 키·focus·취소 이벤트
1. 새 package에서 실제 마우스·키보드·컷/playlist drag 회귀
2. ThemeA/EList 편집 save/delete/close의 세부 확인 문구와 focus 순서 대조
3. 자동 갱신 UI의 실제 실행 상태 기반 통합 테스트와 종료 상태 행렬
4. Debug/Release x64와 새 package에서 실제 마우스·키보드·drag 회귀
4. 다음 Designer handler 묶음의 원본 코드·상태 전이 대조
각 항목은 실제 원본 코드 근거와 회귀 테스트가 모두 생기기 전에는 완료로 판정하지 않는다.

View File

@@ -52,8 +52,8 @@ HELLO/BYE 연결은 2026-07-15에 확인했다. scene/PREPARE/TAKE/PGM은 실행
| 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 대체 | 현재 빌드 실측 제외 | 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-020 | P2 | FarPoint 대체 | 자동 검증 완료·package 실측 대기 | source 단일 행 drag, F8 후보·PROGRAM·DEL 집합·Shift anchor 분리, 종목 ListBox/컷 ListView 키보드와 Windows drag rectangle을 DOM/C# 회귀로 고정. 탭 dragover와 EList reorder는 기존 실측 유지 | 현재 변경을 합친 package에서 playlist 단일 행 drag와 키보드/focus를 다시 실측; 활성 Print/Excel export는 원본에도 없음 |
| LF-021 | P2 | 패키지 단일 인스턴스·모달 격리 | 자동 검증 완료·package 실측 대기 | 두 번째 activation 전달, 강제 종료 없음. child modal Escape no-op, 전역 키 격리, Tab trap, opener/nested-alert focus 복귀와 PList pending close lock을 고정 | 새 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 추정 금지 |
| LF-024 | P0 | Designer 이벤트 전수 감사 | 완료 | 원본 Designer 구독 **186개**, 화면별 고유 핸들러 **171개**를 IMPL 131/DEAD 20/VISUAL 10/DRAG 9/SAFETY 1로 전수 분류 | 상세 줄 단위 근거는 `LEGACY_DESIGNER_EVENT_PARITY_AUDIT.md` |

View File

@@ -170,25 +170,43 @@ drag만 5행 순서를 바꾸는 것도 확인했다. .NET Debug/Release 합계
scene PREPARE/TAKE/PGM을 실행하지 않은 이 Live 연결 회차를 전체 방송 송출 PASS로 확대하지
않는다.
### 코드 대조 Slice 1~9 합본 추가 검증
### 코드 대조 Slice 1~10 합본 추가 검증
이후 원본 이벤트를 다시 코드 단위로 대조해 F8 후보·PROGRAM·DEL 다중선택·Shift anchor를
분리하고, 런타임 컷 50행, 종목/컷 키보드, UC1 tree, s8010 이동평균 재샘플링, K3D raw 인자와
OnCutOut/OnStopAll callback 게이트를 합쳤다. 이 합본의 검증 결과는 다음과 같다.
OnCutOut/OnStopAll callback 게이트에 이어 child modal focus/Escape, PList 완료 순서와
Windows 컷 drag rectangle을 합쳤다. 이 합본의 검증 결과는 다음과 같다.
| 항목 | 추가 검증 결과 |
|---|---|
| .NET Debug x64 | **2,584/2,584** 통과, 실패·건너뜀 0 |
| .NET Release x64 | **2,584/2,584** 통과, 실패·건너뜀 0 |
| Web Node | **456/456** 통과, 실패·취소·건너뜀·TODO 0 |
| .NET Debug x64 | **2,611/2,611** 통과, 실패·건너뜀 0 |
| .NET Release x64 | **2,611/2,611** 통과, 실패·건너뜀 0 |
| Web Node | **491/491** 통과, 실패·취소·건너뜀·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`/`예`/`아니요` 확인 후 정상 종료 |
| Visual Studio 2026 MSBuild | VS **18.7.8**의 x64 MSBuild로 Debug/Release 솔루션 빌드 모두 성공 |
| 최신 unsigned Release MSIX | **15,244,365 bytes**, SHA-256 `A2E9AA95B1F5E20C6E83A3B240A96F0E2792D3A1F809FEC60D829613BDF5CC8E` |
| 패키지 감사 | 78항목, Web 7개 source byte-for-byte 일치, `.t2s`·실설정·라이선스·인증서·K3D vendor 파일 0 |
| package-context DryRun | 최신 Release development registration에서 실행·응답, 인자 없음, `DRY RUN` UI, 앱 소유 TCP 0을 확인. 중립 종료창 `프로그램을 종료하겠습니까?`/`예`/`아니요`를 대조하고 `예`를 1회 Invoke해 정상 종료 |
이 unsigned MSIX는 개발 package-context 실행 검증용이며 위 signed 배포 패키지를 대체하지
않는다. 이 추가 회차에서도 scene/PREPARE/TAKE/NEXT/DB write와 Tornado 연결은 실행하지 않았다.
새 컷 drag와 modal focus는 자동 계약까지 통과했으며 실제 package 마우스 drag 실측은 다음
대조 회차에 남아 있다.
컷 drag 임계값 후속 감사에서는 시작 시 한 번 읽던 `GetSystemMetrics` 캐시를 제거했다.
현재 구현은 `GetSystemMetricsForDpi(SM_CXDRAG/SM_CYDRAG, 96)` 결과를 host DIP 단위로
직렬화하고 `XamlRoot.RasterizationScale`/`window.devicePixelRatio` 비율로 pointer-event CSS
pixel에 맞춘다. 따라서 100%·150% host DPI뿐 아니라 이전 profile에 남은 browser zoom도
보정하며 비정상 비율은 1로 닫힌다. `WM_SETTINGCHANGE`·`WM_DISPLAYCHANGE`·`WM_DPICHANGED`
모든 state 게시 경계에서 값을 새로 읽고, cut-pointer-down 회신은 아직 시작되지 않은 gesture
rectangle도 최신 값으로 다시 만든다. 이후 browser/pinch zoom 입력은 비활성화하며, native
API를 사용할 수 없는 환경에서는 축별 4 DIP로 닫힌다.
PList 후속 대조에서는 WinForms `DataSource` 재지정의 첫 행 자동 선택을 복원하고, 행 선택에도
`named-ui-*` requestId와 exact command/target/sequence receipt를 적용했다. 따라서 2행 click
직후 확인·삭제가 과거 1행에 실행되지 않으며, double-click은 동일 ID의 선택 완료 뒤 한 번만
이어진다. known commit/read-back 실패는 warning을 유지한 성공으로, 현재 입력 검증 실패는
revision 미증가 실패로 구분하는 자동 회귀도 통과했다.
## 판정 경계

View File

@@ -120,6 +120,8 @@ Theme 결과 double-click은 원본의 5행 현재가 경로를 사용한다. PL
|---|---|---|
| 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`가 현재 순서와 일치할 때만 교환하므로 같은 메시지의 중복 전달이 순서를 되돌리지 않는다. |
| 컷 `ListView.MouseDown/MouseMove` | pointer capture와 Windows `SM_CXDRAG/SM_CYDRAG` 비대칭 rectangle | Chromium native drag 임계값을 쓰지 않는다. DPI-aware API의 96-DPI 값을 host DIP로 전달하고 `XamlRoot.RasterizationScale`/`devicePixelRatio` 비율로 retained browser zoom까지 보정한다. Windows 설정·화면·DPI 변경 및 state 게시 때 갱신하며, left/top 포함·right/bottom 제외 경계를 넘은 뒤 playlist 안에서 놓을 때만 현재 C# 선택 집합을 physical order로 한 번 추가한다. |
| PList `DataSource`/click/double-click | non-empty 목록 첫 행 자동 선택, 행 click 즉시 선택, double-click save/load | 열기·새로고침·추가·삭제 뒤 첫 행을 선택한다. 다른 행 click은 exact requestId 영수증 전까지 확인·삭제를 잠그고 동일 opaque ID의 double-click만 선택 완료 뒤 실행해 이전 행 DB mutation을 차단한다. |
| UC6 `FpSpread2.AllowDragDrop` | ThemeA/EList 편집행의 내부 custom MIME와 opaque `rowId` | 워크플로가 임의 before/after 위치를 계산한다. 존재하지 않는 ID, 같은 행, 추가 payload 필드는 strict bridge에서 거부하거나 no-op 처리한다. |
실제 마우스 검증에서는 playlist 행 drop이 조상 drop zone으로 버블링해 현재 선택된 컷을