Files
HMDesign/docs/org-chart/implementation-report-p9-3.md

149 lines
14 KiB
Markdown

# HMDesign OrgChart P9.3 — Pan 유지 · 회사 안팎 소속 변경 Drag & Drop
RESULT: **PASS — P9.3에서 요청한 기능 변경 범위.** 회사 간 이동 금지 조건은 2026-10-13 사용자의 최신 요청으로 취소되었다. Guard의 NOT VERIFIED와 디자인 승인 상태는 기능 PASS와 별도로 보존한다.
## 작업 기준·시간
- 범위: 기존 Expanded React Storybook과 공유 Vanilla Core의 Auto 동작. 신규 업무 화면·Story·Navigation·서버 API 없음.
- 작업 폴더: `D:\디자인시스템\HMDesign-OrgChart`
- 브랜치: `feature/orgchart-package`
- 시작/완료 HEAD: `7614507583927a25a270963a74ff044be0557b9f`
- origin: `https://gitea.hmac.kr/b25027/HMDesign.git`. 원격 조회·Fetch·Pull 없음.
- Started At: 2026-10-13 11:11:19 KST
- Completed At: 2026-10-13 11:50:58 KST
- Actual Duration: 39분 39초
- Estimated Duration: 최초 50~70분 — 분석10분 / 구현25~35분 / 통합 검증15~25분. 회사 간 허용 정책 변경 시 추가15~25분 예상 안내.
- Variance: 최초 예상보다 10분21초~30분21초 짧음.
## 엔진·입력 처리
기존 `orgchart@6.0.0`의 `filterAllowedDropNodes`, `dropHandler`, `dragendHandler` 및 `nodedrop.orgchart`/`otherdropped.orgchart` 이벤트를 재사용한다. 이벤트의 기본 DOM 변경은 `preventDefault()`로 취소하고 검증한 불변 데이터로 Core를 갱신한다. 여러 회사는 독립 엔진 인스턴스이므로 모든 표시 트리에 Drop 후보를 계산하고 다른 회사 트리의 대상도 최종 검증한다. 새로운 DnD 의존성을 추가하지 않았다.
Core의 Pointer 처리에서 화면 좌표6px 임계값, Pointer capture, 실제 Shadow DOM hit-test를 담당한다. 빈 공간에서 시작하면 기존 Pan, 직원 카드에서 시작하면 직원 이동, 그룹 타이틀에서 시작하면 그룹 이동이다. 임계값 미만의 클릭은 기존 직원 팝업·그룹 접기를 실행한다. Drag 후 발생하는 포인터 click은 차단하며 키보드 click은 유지한다. ESC·Pointer cancel·포커스 상실은 이동을 취소한다. Viewport 밖 Drop은 데이터 변경 없이 거부한다.
브라우저 touch 에뮬레이션에서 카드 Drag와 빈 공간 Pan 모두 PASS. 자식의 implicit touch capture 변경을 상위 Viewport 취소로 오인하지 않도록 대상 요소를 구분한다. 실기기 터치는 NOT VERIFIED다.
## 소속 변경 계약·결과
기존 `organization.companyId`는 조직 트리의 회사, `employee.affiliationId`는 직원의 관계사 소속이다. 조직의 회사 값은 기존 상위 조직 상속 계약으로 판정하며, 같은 회사 이동에서 파견 등 기존 affiliation을 덮어쓰지 않는다. 기존 필드만 사용하고 데이터 스키마를 확장하지 않았다.
| 이동 | 실제 데이터 변경 | 보존 |
|---|---|---|
| 같은 회사 직원 | 대상 `organizationId` | `affiliationId`, 직원 ID·개인정보·전체 인원 |
| 회사 간 직원 | 대상 `organizationId`와 대상 회사 `affiliationId` | 직원 ID·개인정보·전체 인원 |
| 같은 회사 그룹 | 그룹 `parentId` | 하위 관계·회사·직원 소속·조직장·ID |
| 회사 간 그룹 | 그룹 `parentId`, 전체 하위 조직 `companyId`, 전체 하위 직원 `affiliationId` | 하위 조직 연결·조직장·ID·직원 총원 |
회사 간 예시에서 `virtual-006`을 `saman-division-1`로 옮기면 실제 `organizationId=saman-division-1`, `affiliationId=saman`이 된다. Story의 회사별20명·한맥기술/삼안 표시 조건에서 한맥의10명 하위 그룹을 삼안으로 옮기면 양쪽 집계는 **한맥20→10명, 삼안20→30명, 선택 합계40명 유지**다. 숨긴5개 회사를 포함하는 세션 전체140명도 유지된다. 조직·직원 ID 중복은 없다.
다음 이동은 이유를 표시하고 데이터 변경 없이 거부한다: 조직장 직원, 회사 Root, 자기 자신, 자손 조직, 기존 부모/소속, 없는 source/target, 잘못된 데이터 및 회사 참조. 회사 간 이동 대상의 affiliation 항목이 없으면 임의 소속을 만들지 않고 거부한다. 회사 Root는 이동 대상이 될 수 있지만 Root 자체를 옮길 수 없다.
성공 시 검증된 새 데이터는 깊이 동결된 브라우저 상태가 된다. `OrgChartHandle.move()`, `getData()`, `onMove`를 통해 Vanilla/React에서 같은 정책을 사용한다. 원본 DataSource·JSON·서버·DB·localStorage에 기록하지 않는다. 전체 페이지 재로딩은 원래 데이터로 돌아간다.
## 재배치·색상·집계·팝업
현재 데이터를 기준으로 기존 자동 레이아웃, 부모 연결선, 직접/하위 포함 인원, 회사별 인원, affiliation 현황과 직원 팝업 context를 다시 만든다. 성공한 이동 시 열린 React HM Dialog를 닫아 이전 소속 정보가 남지 않으며, 다시 클릭하면 현재 조직·회사 정보를 표시한다. 기존 회사 palette에서 대상 회사 main color를 그룹 제목과 직원의 연결 방향 강조 Border에 적용한다. palette·카드 SVG·카드 크기 변경 없음.
이동 시 Zoom/Pan 값과 기존 조직 ID 기준 접기 상태를 보존한다. 숨긴 조직의 Drop 후보는 현재 표시 범위에서 제외한다. 이동 후 Fit은 다시 계산한 forest 전체를 포함한다. 회사 선택은 세션의 표시 필터로 처리하므로 숨겼다 다시 표시해도 소속 변경이 유지된다. `데이터 적용`은 지정 조건의 샘플을 다시 만들며 `초기화`는 최초 설정/소속/이동 안내로 돌아간다.
| 표시 유형 | 같은 회사 직원/그룹 | 회사 간 직원 | 회사 간 하위 그룹 | 연결선·Border·Fit |
|---|---|---|---|---|
| TB | PASS | PASS | PASS | PASS |
| LR | PASS | PASS | PASS | PASS |
| Hybrid | PASS | PASS | PASS | PASS |
Hybrid의 회사 간 직원 이동은 별도 실제 포인터 시나리오에서 회사·조직·색상·양쪽 집계·HM Dialog 갱신을 확인했다. Hybrid 시작 Depth 전체 조합, 특정 그룹 세로 배치 전체 조합은 NOT VERIFIED다. Compact 지원 범위는 기존과 동일하다.
TB에서 Zoom100%·65%·140%, Fit 이후 Pan한 상태의 Drop을 확인했다. LR/Hybrid 대표 실제 그룹 Drag와 회사 간 이동 후 세 유형의 Fit 포함 검사를 확인했다. 모든 Zoom×모드 조합을 재검증하지 않았다.
## 접근성·화면
직원 카드/그룹 타이틀에 초점을 둔 뒤 **Alt+M → 방향키 또는 Home/End로 대상 선택 → Enter/Space로 이동**, ESC/Tab으로 취소할 수 있다. 이동 가능·거부 후보는 선 모양과 텍스트로도 구분하고 live 안내를 제공한다. 성공 후 이동된 카드 또는 대상 제목으로 초점이 이동하며, 거부·취소 시 source 초점을 복구한다. Collapse가 꺼져 있어도 제목을 이동 source로 사용할 수 있다. 직원 팝업이 꺼져 있어도 키보드 이동을 위한 카드 초점을 제공한다.
Story는 기존 HM FilterBar·Button·Checkbox·Popover·Label·Select·Separator와 실제 HM Dialog를 유지한다. 데이터 조건은 기존 FilterBar API와 맞으므로 그대로 재사용한다. 기존 네 메뉴만 유지하며 신규 Navigation은 없다. 최종 회사별 인원/이동 안내를 390·768·1280·1440px에서 확인해 문서와 텍스트 overflow가 없었다. 실제 스크린리더·운영체제 보조기술 검증은 NOT VERIFIED다.
## 통합 검증
| 검사 | 결과 | 근거 |
|---|---|---|
| 새 이동 정책 단위 테스트 | 11/11 PASS | `node packages/org-chart/test.mjs src/data/moves.test.ts`, 실행 출력은 도구 기록에 보존 |
| Vanilla Core 변경 검사 | 51개 고유 assertion PASS / FAIL0 | [통합 결과](../../test-results/org-chart-p9-3/verification-summary.json), `core-browser*.json` |
| 실제 React Storybook 이동/입력 검사 | 117개 고유 assertion PASS / FAIL0 | 같은 통합 결과, `browser-editing*.json` |
| Fixed·기존 메뉴·최초 반응형 최소 회귀 | 9/9 PASS | [원본](../../test-results/org-chart-p9-3/preservation.json) |
| 최신 안내·회사별 집계 반응형 | 5/5 PASS | [원본](../../test-results/org-chart-p9-3/responsive-current.json) |
| Core Typecheck·Build | PASS | `typecheck-core.log`, `build-core.log` |
| React Typecheck·Build | PASS | `typecheck-react.log`, `build-react.log`; 최종 React 소스 기준 빌드 유지 |
| 변경 Story Typecheck·Storybook production build | PASS | `story-tsconfig.json`, `typecheck-story.log`, `build-storybook.log` |
| 신규 console/page 오류 | 0 | 각 브라우저 실행의 오류 결과 |
| JSON·palette·원본 artwork 불변 / 기존 작업 보존 | PASS | [SHA-256·Git 감사](../../test-results/org-chart-p9-3/final-state.json) |
| `git diff --check` | PASS | 최종 변경 상태 검사 |
고유 assertion 합계는 원본 실행 횟수의 합이 아니다. 같은 이름은 최종 결과로 교체했다. Reset의 잘못된 root selector 검사는 명시적 이름 대응표로 수정된 실제 Root/원래 소속 검사의 PASS를 연결했다. 최신 정책으로 취소된 회사 간 거부 검사6건은 최종 합계에서 제외하고 원본을 보존한다. 단위 테스트와 브라우저 assertion 수를 하나의 테스트 수로 합하지 않는다.
최초 실제 Drag hit-test가 detached fragment를 참조한 문제와 touch capture 문제는 소스 수정 후 실패 범위만 재검증했다. LR 검증에서 Pan 첫 좌표가 Viewport 밖으로 나간 문제와 Reset 검증의 존재하지 않는 root selector는 검증 코드만 수정했다. 초기 FAIL/진단 JSON을 삭제하거나 PASS로 덮어쓰지 않았다. 현재 통합 기능 결과에 미해결 FAIL 없음. 기존 P7~P9.2 전체 브라우저 suite·20/100/300명 전체 조합을 반복하지 않았다.
### Guard·미검증
최종 `STORYBOOK / PARTIAL` Guard 대상은 변경된 Expanded Story와 Auto editing CSS다. [Guard 원본](../../test-results/org-chart-p9-3/guard.json)의 **FAIL0 / status NOT VERIFIED / staticStatus NOT VERIFIED / runtimeStatus NOT VERIFIED**를 유지한다.
- `SCOPE-001`: package CSS가 증명된 Story graph 밖이다. 별도 Core build와 실제 Shadow DOM 브라우저 검사 결과를 제공한다.
- 기존 `TOKEN-001`: Radix의 `--radix-popover-content-available-height` 선언 범위.
- 기존 `TOKEN-002`: 공통 StoryPage의 동적 className.
- `styleEvidence`: **STATIC PASS / RUNTIME NOT VERIFIED / DESIGN_INTENT NOT VERIFIED / FINAL NOT VERIFIED**. 결합 `styleVerification`도 NOT VERIFIED다.
브라우저에서 회사 색상과 Border 및 editing token을 확인한 것은 해당 기능의 런타임 근거다. 전체 HM CSS 결합 디자인 승인으로 해석하지 않는다. 기존 P9~P9.2 전체 Guard FAIL/NOT VERIFIED와 보고서는 변경하지 않았다. 사용자 디자인 승인, 실제 터치 기기, 실제 스크린리더, Chrome 외 브라우저, 전체 레이아웃/옵션 조합, 300명 이상 Drag 성능, 서버 저장·SSR은 NOT VERIFIED다.
## Fixed 보호·변경 파일
Fixed Full Composition의 DOM hash, 이미지139개 SHA-256, 요소152개 computed style/정규화 좌표가 P9.2 최종 스냅샷과 일치했다. 중앙 정렬·직원 카드43개·연결경로5개·충돌0 유지. Fixed에 편집 상태·Drop 후보·안내를 추가하지 않았다. 이 검사 이후 회사 간 정책 변경은 Auto 데이터/bridge/Story에 한정되므로 정상 Fixed 검사를 반복하지 않았다.
시작 시 미커밋 authored 파일47개를 SHA-256과 패치로 보존했다. 그중 관련13개에 P9.3을 추가하고 나머지34개는 byte 단위로 동일하다. Full Composition Story·P9.2 보고서·기존 카드·palette·fixture·lockfile·package dependency는 변경하지 않았다. Git 전체 diff에는 누적 변경이 포함되므로 이번 범위를 Git 전체 경로 목록으로 간주하지 않는다.
관련 기존 파일13개:
```text
docs/org-chart/expanded-usage.md
packages/org-chart-react/README.md
packages/org-chart-react/src/OrgChart.tsx
packages/org-chart/README.md
packages/org-chart/src/core/engine.ts
packages/org-chart/src/core/expanded-content.ts
packages/org-chart/src/core/expanded-viewport.ts
packages/org-chart/src/core/index.ts
packages/org-chart/src/core/types.ts
packages/org-chart/src/core/vendor.d.ts
packages/org-chart/src/data/index.ts
packages/org-chart/src/styles/org-chart-expanded.css
src/org-chart/org-chart-expanded.stories.tsx
```
새 authored 파일7개:
```text
docs/org-chart/implementation-report-p9-3.md
examples/org-chart/verify-p9-3-core.mjs
examples/org-chart/verify-p9-3-preservation.mjs
examples/org-chart/verify-p9-3.mjs
packages/org-chart/src/core/expanded-editing.ts
packages/org-chart/src/data/moves.test.ts
packages/org-chart/src/data/moves.ts
```
`packages/*/dist`, `storybook-static`, `test-results`, `_local`, logs 등은 검증 생성물이며 authored 변경 목록에서 제외한다. 기존 baseline 기록은 `_local/p9-3/baseline-files.json`과 `baseline.patch`에 남겼다.
**Stage·Commit·Push·Merge·main checkout·PR·Reset·Clean·Rebase·Force Push 미진행.** Staged0, 시작/완료 branch·HEAD 동일. P9.3에서 종료하며 P10을 시작하지 않았다.
## 실제 Storybook·캡처
- [Expanded OrgChart](http://127.0.0.1:6064/?path=/story/orgchart--expanded-org-chart): 실제 React production Storybook.
- [TB 회사 간 그룹 이동 후 Fit](../../test-results/org-chart-p9-3/tb-cross-company-fit.png)
- [LR 회사 간 그룹 이동 후 Fit](../../test-results/org-chart-p9-3/lr-cross-company-fit.png)
- [Hybrid 회사 간 그룹 이동 후 Fit](../../test-results/org-chart-p9-3/hybrid-cross-company-fit.png)
- [직원 이동 후보](../../test-results/org-chart-p9-3/tb-staff-drop-hover.png) / [직원 이동 완료](../../test-results/org-chart-p9-3/tb-staff-moved.png)
- [키보드 이동 완료](../../test-results/org-chart-p9-3/keyboard-move-complete.png)
- [최신390px](../../test-results/org-chart-p9-3/expanded-current-390.png) / [768px](../../test-results/org-chart-p9-3/expanded-current-768.png) / [1280px](../../test-results/org-chart-p9-3/expanded-current-1280.png) / [1440px](../../test-results/org-chart-p9-3/expanded-current-1440.png)
- [Fixed 보존1440px](../../test-results/org-chart-p9-3/fixed-preserved-1440.png)
- [공유 Vanilla Core](../../test-results/org-chart-p9-3/vanilla-cross-company.png)
미리보기 서버는 `_local/p9-3/serve.mjs`로 실행한 로컬6064 세션이다. 정적 Storybook 배포나 외부 공개는 수행하지 않았다.