55 KiB
HMDesign OrgChart 공통 사용 가이드
개발자·Codex·Claude가 현재 공개 API로 조직 데이터를 연결하기 위한 가이드다. 구현 기준은 feature/orgchart-package의 5acaf8a29596317133e54e8ad437ee00beba1cf0(2026-10-13)과 P10.2에서 통합한 React Wrapper Entry 개선이다. P10.3의 Git 공유 대상은 이 Feature 브랜치이며 실제 받은 버전은 git rev-parse HEAD로 확인한다. 과거 P-AI1의 구버전 제한을 현재 지원 여부로 사용하지 않는다.
빠른 시작은 아래 데이터 파일과 React 또는 Vanilla 진입점을 함께 복사한다. 예제는 Vite의 TypeScript 환경을 전제로 한다. 이 가이드의 기본 예제는 현재 Auto 컴포넌트를 사용하는 예제이며, 외부 UI가 없는 Chart Only 구현 완료를 뜻하지 않는다.
1. 설치·패키지·실행 환경
@hm-design/org-chart@0.1.0: 데이터 타입·Validator·JSON DataSource·집계·이동 함수·DOM 렌더러. React 없이 사용한다. 내부 엔진 의존성은orgchart@6.0.0이며 소비자가 엔진을 직접 조작하지 않는다.@hm-design/org-chart-react@0.1.0: 위 Core에 의존하는 React lifecycle adapter. 조직 배치·카드·데이터 검증을 다시 구현하지 않는다. React peer는^19.0.0, HM Dialog 선택에 사용하는@hm-design/ui@0.1.0peer는 optional이다.- 저장소는 npm workspaces다. Git Clone 후 소스에서 설치·빌드한다. 외부 Registry에 두 패키지가 게시됐다는 증거는 이번 범위에 없다.
- Core 렌더링은 DOM·Shadow DOM·ResizeObserver·native dialog를 사용하는 브라우저 작업이다. SSR 중에는 mount하지 않는다. React 호스트도 client 경계에서 렌더링한다.
Feature 브랜치 Git Clone
Git, Node.js와 npm, 공식 Gitea 및 npm 레지스트리 접근이 필요하다. Node.js 조건은 ^20.19.0 || >=22.12.0이며 Node 21은 제외한다. P10.3 실행 환경은 Node 22.23.2 / npm 10.9.8이다. 기존 폴더를 덮어쓰지 않고 새 경로에서 실행한다. ZIP·별도 설치 파일·다른 작업 폴더의 node_modules/dist 복사는 필요하지 않다.
git clone --branch feature/orgchart-package --single-branch https://gitea.hmac.kr/b25027/HMDesign.git HMDesign-OrgChart
cd HMDesign-OrgChart
git branch --show-current
git rev-parse HEAD
node --version
npm.cmd --version
npm.cmd ci
npm.cmd run build:packages
npm.cmd run typecheck --ignore-scripts
npm.cmd run test:org-chart
npm.cmd run build --ignore-scripts
--ignore-scripts는 위에서 완료한 package build를 pretypecheck/prebuild에서 반복하지 않기 위한 옵션이다. 이 위치에서 Typecheck/Build 본문은 실제로 실행된다. 루트 설치는 Storybook/HM UI를 포함하는 개발 workspace 설치이며 Core 소비자의 최소 의존성을 의미하지 않는다.
Vanilla 예제는 아래 build 명령 후 dev:org-chart-example로 실행한다. React 예제는 package build 후 build:org-chart-react-example, dev:org-chart-react-example을 사용한다. HM Dialog Reference는 npm.cmd run storybook의 Expanded OrgChart에서 확인한다. 포트가 사용 중이면 기존 서버를 재사용한 결과를 독립 Clone 성공으로 보고하지 않는다.
저장소의 설치 절차를 완료한 환경에서 사용하는 명령이다. Windows에서는 npm.cmd를 사용한다.
npm.cmd run build:org-chart
npm.cmd run build:org-chart-react
npm.cmd run build:org-chart-example
npm.cmd run dev:org-chart-example
# 또는 React 예제 서버
npm.cmd run dev:org-chart-react-example
위 명령을 실행하면 기존 예제/산출물을 갱신할 수 있다. 병렬 문서 작업의 검증 명령으로 실행하지 않는다. 실행 위치·포트는 Vanilla 예제 서버, React Vite 설정을 확인하고 기존 서버와 충돌시키지 않는다.
외부 프로젝트 최소 설치
외부 호스트의 최소 의존성을 검증할 때는 Clone한 소스에서 직접 생성한 npm pack 산출물을 로컬 설치한다. tarball을 별도 전달하거나 Registry publish를 요구하지 않는다. 위 package build가 완료된 Clone 루트에서 다음을 실행한 뒤 아래 설치 경로를 생성된 파일 위치로 바꾼다.
New-Item -ItemType Directory -Path test-results/local-pack -Force
npm.cmd pack -w @hm-design/org-chart --ignore-scripts --pack-destination test-results/local-pack
npm.cmd pack -w @hm-design/org-chart-react --ignore-scripts --pack-destination test-results/local-pack
npm.cmd pack -w @hm-design/ui --ignore-scripts --pack-destination test-results/local-pack
생성물은 Git ignored 로컬 검증 자료다. A/B/C는 별도 호스트 폴더에서 아래처럼 설치한다. Registry publish 여부는 확인되지 않았다.
# A: Core + Vanilla. React/HMDesign UI 없이 설치한다.
npm.cmd install ./hm-design-org-chart-0.1.0.tgz
# B: Core + React Wrapper. HMDesign UI/Theme/Tailwind 없이 설치한다.
npm.cmd install ./hm-design-org-chart-0.1.0.tgz ./hm-design-org-chart-react-0.1.0.tgz react@19.3.0 react-dom@19.3.0
# C: HM Dialog를 명시적으로 사용할 때만 HMDesign UI와 해당 peer를 추가한다.
npm.cmd install ./hm-design-ui-0.1.0.tgz
Core와 Wrapper의 package.json에서 실제 dependency/peer를 확인한다. C의 HM Theme/Tailwind 연결은 호스트가 제공하며 /hm-dialog가 CSS를 자동 import하지 않는다. 기본 Entry는 HM UI runtime/type 의존 그래프를 포함하지 않는다. 설치 여부로 popup을 자동 전환하지 않는다.
공개 Export와 CSS
Core package의 exports는 ., ./style.css, ./engine.css, ./browser다.
- 주 진입점:
createOrgChart,JsonOrgChartDataSource,OrgChartSourceError,validateOrgChartDataset,isSafeImageUrl,normalizeOrgChartDataset,calculateSummary,snapshotOrgChartDataset. - 색상:
companyColorPresets,getCompanyColorPreset; 타입:CompanyId,CompanyColorPreset. - 가상 예제:
createOrgChartExampleDataset및 관련 타입. - 이동:
createOrgChartMoveValidator,validateOrgChartMove,applyOrgChartMove및 관련 타입. - 독립 모듈:
createOrgChartTree,createPositionSummary,createAffiliationSummary,createStatusSummary. 이들은 Full Composition의 Auto 기능 전체를 제공하는 별도 React 컴포넌트가 아니다. - 데이터·옵션·Handle 타입은 같은 주 진입점에서 가져온다. 내부
src/dist경로를 소비 코드에서 import하지 않는다.
React 기본 @hm-design/org-chart-react의 runtime export는 OrgChart 하나이며 HMDesign UI가 필요 없다. 선택 Entry @hm-design/org-chart-react/hm-dialog는 OrgChart와 HMEmployeeDialog를 export한다. 두 Entry 모두 OrgChartProps, OrgChartEmployeeSelection, OrgChartEmployeePopupProps와 Core 공개 타입을 type-only로 export한다. JsonOrgChartDataSource 같은 runtime helper는 Core에서 import한다. 내부 src/dist Import는 사용하지 않는다.
stylesheetUrls: { engine, core }는 필수다. Core가 자기 Shadow DOM에 engine CSS → core CSS 순으로 link를 넣는다. document에 CSS를 import하는 것만으로 내부 카드가 스타일링되지는 않는다. 아래 ?url은 Vite 전용이며 다른 bundler에서는 공개 CSS를 실제 배포 URL로 해석해 전달한다.
React 없는 일반 HTML도 공개 browser bundle을 HTTP 서버로 배포해 사용할 수 있다. ./browser는 ESM named import용이 아니라 build가 생성하는 IIFE HMOrgChart 전역 bundle이다. 이 가이드의 Vanilla TypeScript 예제는 주 ESM 진입점을 사용한다. URL JSON은 HTTP(S)/상대 경로를 사용하며 file:// fetch는 지원하지 않는다.
근거: Core manifest, Core exports, Data exports, React manifest, React exports, Core build.
2. 빠른 시작용 데이터 — 예제 A / B / E
다음은 가상 데이터 예제다. data.ts로 저장한다. 예제의 회사·분기 수를 제품 기본값으로 사용하지 않는다. A는 1회사·2본부·본부별 2팀·총 7명(대표 1명 + 본부장 2명 + 팀 직원 4명)이다. B는 회사별 독립 Root 두 개와 회사별 컬러를 보여준다. E는 동일한 singleCompany를 React와 Vanilla에서 사용한다.
업무 명칭인 본부를 이 예제에서는 office로 대응한다. Contract에 headquarters enum을 새로 추가하지 않는다.
// data.ts
import {
getCompanyColorPreset, validateOrgChartDataset,
type CompanyId, type Employee, type Organization, type OrgChartDataset,
} from '@hm-design/org-chart';
export function makeCompanyDataset(
companyId: CompanyId, teamCounts: readonly number[],
): OrgChartDataset {
if (teamCounts.some(count => !Number.isSafeInteger(count) || count < 0)) {
throw new Error('팀 수는 0 이상의 정수여야 합니다.');
}
const name = getCompanyColorPreset(companyId).companyName;
const rootId = companyId + '-root';
const organizations: Organization[] = [];
const employees: Employee[] = [];
const addEmployee = (id: string, organizationId: string, position: string) => {
employees.push({
id, organizationId, affiliationId: companyId, name: '가상 ' + id,
position, jobTitle: null, profileImage: null, workStatus: 'active',
});
};
addEmployee(companyId + '-ceo', rootId, '대표이사');
organizations.push({
id: rootId, companyId, parentId: null, name, type: 'root',
leaderId: companyId + '-ceo', displayOrder: 0,
});
teamCounts.forEach((count, index) => {
const officeId = companyId + '-office-' + (index + 1);
const leaderId = officeId + '-leader';
addEmployee(leaderId, officeId, '본부장');
organizations.push({
id: officeId, parentId: rootId, name: (index + 1) + '본부',
type: 'office', leaderId, displayOrder: index,
});
for (let team = 0; team < count; team++) {
const teamId = officeId + '-team-' + (team + 1);
organizations.push({
id: teamId, parentId: officeId, name: (team + 1) + '팀',
type: 'team', leaderId: null, displayOrder: team,
});
addEmployee(teamId + '-employee', teamId, '사원');
}
});
const data: OrgChartDataset = {
schemaVersion: '0.1-draft', organizations, employees,
affiliations: [{ id: companyId, name }],
metadata: { asOfDate: '2026-10-13', dataVersion: 'example-1' },
};
const checked = validateOrgChartDataset(data);
if (!checked.valid) throw new Error(JSON.stringify(checked.issues));
return checked.data;
}
export const singleCompany = makeCompanyDataset('hanmac', [2, 2]);
const secondCompany = makeCompanyDataset('saman', [1]);
export const multiCompany: OrgChartDataset = {
...singleCompany,
organizations: [...singleCompany.organizations, ...secondCompany.organizations],
employees: [...singleCompany.employees, ...secondCompany.employees],
affiliations: [...singleCompany.affiliations, ...secondCompany.affiliations],
metadata: { ...singleCompany.metadata, dataVersion: 'example-multi-1' },
};
const checkedMulti = validateOrgChartDataset(multiCompany);
if (!checkedMulti.valid) throw new Error(JSON.stringify(checkedMulti.issues));
Root의 companyId는 하위 조직이 상속한다.복수 회사는 parentId: null인 별도 Root를 만든다. 회사들을 잇는 가짜 상위 조직을 추가하지 않는다. B는 위 multiCompany를 같은 렌더러에 전달하면 된다.
3. React 최소 사용 — 예제 D / E
Vite React 프로젝트에서 아래 App.tsx, main.tsx, index.html과 앞의 data.ts를 사용한다. 기본 조회용 설정은 dragDropEnabled={false}다. Zoom/Pan/팝업은 별도 설정으로 계속 사용한다.
// App.tsx
import { OrgChart } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';
const source = new JsonOrgChartDataSource(singleCompany);
const stylesheetUrls = { engine: engineUrl, core: coreUrl };
const autoLayout = { type: 'tb' as const, initialVisibleDepth: 'all' as const };
export default function App() {
return <OrgChart
dataSource={source} stylesheetUrls={stylesheetUrls}
layoutMode="auto" displayMode="readOnly" autoLayout={autoLayout}
dragDropEnabled={false} employeePopup="core"
onStateChange={state => {
if (state.status === 'error') console.error(state.issues);
}}
onLifecycleError={error => console.error(error)}
/>;
}
// main.tsx
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
if (!container) throw new Error('root 요소가 없습니다.');
const root = createRoot(container);
root.render(<App />);
window.addEventListener('pagehide', () => root.unmount(), { once: true });
<!-- React index.html -->
<!doctype html>
<html lang="ko">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>OrgChart</title></head>
<body style="margin:0">
<div id="root"></div>
<script type="module" src="/main.tsx"></script>
</body>
</html>
React가 전용 div와 Core handle의 mount·subscribe·destroy를 소유한다. 소비자가 같은 div에 다른 자식이나 별도 Core 인스턴스를 넣지 않는다. StrictMode도 wrapper가 lifecycle을 관리한다. 공개 ref/imperative handle prop이나 data, title, children prop은 없다.
기본 Entry의 core 모드는 Core CSS로 동작하며 HMDesign UI/Theme/Tailwind를 설치하지 않아도 된다. 실제 tarball을 설치한 React 19.3.0/Vite 8.3.0 환경의 타입·Production Build 검증은 최소 설치 보고서를 참조한다. 다른 bundler·React major의 지원을 이 결과로 확대하지 않는다.
HMDesign Dialog 선택형 연동
기존 HM Dialog 소비자는 Import를 /hm-dialog로 변경하고 employeePopup="hm-dialog"를 유지한다. 기본 Entry에 HM UI가 설치되어 있어도 자동 전환하지 않으며, HM Entry만 Import해도 기본 popup은 Core다. 실제 @hm-design/ui와 호스트의 index.css → hm-theme.css/Tailwind 연결이 필요하다.
// HMChart.tsx
import { OrgChart } from '@hm-design/org-chart-react/hm-dialog';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';
const source = new JsonOrgChartDataSource(singleCompany);
export function HMChart() {
return <OrgChart dataSource={source}
stylesheetUrls={{ engine: engineUrl, core: coreUrl }}
layoutMode="auto" dragDropEnabled={false} employeePopup="hm-dialog" />;
}
외부 Popup Adapter
기본 Entry에 employeePopup="adapter"와 employeePopupAdapter를 함께 전달한다. Adapter는 { selection, onClose }를 받고 표시·닫기·접근성·focus 복귀를 소유한다. selection에는 직원 원본과 context가 있다. 아래 native dialog는 호스트가 소유하는 예시이며 조직도 카드·Core·데이터를 수정하지 않는다.
// CustomChart.tsx
import { useEffect, useRef } from 'react';
import { OrgChart, type OrgChartEmployeePopupProps } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';
function EmployeePopup({ selection, onClose }: OrgChartEmployeePopupProps) {
const dialog = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (!selection) return;
const element = dialog.current!;
element.showModal();
return () => {
element.close();
if (selection.context.trigger.isConnected) selection.context.trigger.focus();
};
}, [selection]);
return selection ? <dialog ref={dialog} aria-label="직원 정보"
onCancel={event => { event.preventDefault(); onClose(); }}>
<p>{selection.employee.name} · {selection.employee.position}</p>
<button type="button" onClick={onClose}>닫기</button>
</dialog> : null;
}
const source = new JsonOrgChartDataSource(singleCompany);
export function CustomChart() {
return <OrgChart dataSource={source}
stylesheetUrls={{ engine: engineUrl, core: coreUrl }} layoutMode="auto"
employeePopup="adapter" employeePopupAdapter={EmployeePopup} />;
}
Adapter 전달만으로는 활성화되지 않는다. 기본 Entry에서 hm-dialog/adapter를 선택하고 Adapter를 제공하지 않으면 Core popup을 유지하고 onLifecycleError로 구성 오류를 알린다. onEmployeeClick이 지정되면 이 선택보다 우선한다. Fixed는 기존 Core popup을 유지하고 외부 popup은 onEmployeeClick으로 연결한다.
근거: Props, Wrapper lifecycle, HM Dialog.
4. Vanilla TypeScript 최소 사용 — 예제 D / E
React가 없는 Vite TypeScript 프로젝트에서 data.ts, 아래 main.ts과 index.html을 사용한다. 반환 cleanup은 페이지/호스트 lifecycle 종료 시 호출한다.
// main.ts
import { createOrgChart, JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';
export async function mountChart(container: HTMLElement): Promise<() => void> {
const chart = createOrgChart({
container, dataSource: new JsonOrgChartDataSource(singleCompany),
stylesheetUrls: { engine: engineUrl, core: coreUrl },
layoutMode: 'auto', displayMode: 'readOnly', dragDropEnabled: false,
autoLayout: { type: 'tb', initialVisibleDepth: 'all' },
onStateChange: state => {
if (state.status === 'error') console.error(state.issues);
},
});
try {
await chart.mount();
const state = chart.getState();
if (state.status !== 'ready') throw new Error('조직도를 표시할 수 없습니다.');
// Auto에서 필요할 때 호출: 현재 전체 표시 영역을 맞춘다.
chart.fitToView();
return () => chart.destroy();
} catch (error) {
chart.destroy();
throw error;
}
}
const container = document.getElementById('chart');
if (!container) throw new Error('chart 요소가 없습니다.');
const dispose = await mountChart(container);
window.addEventListener('pagehide', dispose, { once: true });
<!-- Vanilla index.html -->
<!doctype html>
<html lang="ko">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>OrgChart</title></head>
<body style="margin:0">
<div id="chart"></div>
<script type="module" src="/main.ts"></script>
</body>
</html>
createOrgChart() 자체는 DOM/요청 부작용 없이 handle을 만든다. mount(container?)는 연결된 HTMLElement 하나를 소유하고 첫 load까지 수행한다. 다른 handle이 소유한 컨테이너, 중복 mount, destroy 후 재사용은 거절한다. destroy()는 여러 번 호출해도 안전한 최종 종료이며 다시 사용할 때는 새 handle을 만든다.
Core의 subscribe(listener)는 unsubscribe 함수를 반환하며 이후 상태 변경을 구독한다. getState()는 현재 상태를 읽는다. Auto의 getData()는 숨긴 회사와 로컬 이동을 포함한 현재 immutable 데이터, getSelectedCompanies()/getAutoLayout()/getInteractions()/getDragDropEnabled()는 현재 옵션을 조회한다. getData()는 Auto 데이터가 준비되지 않으면 undefined다.
mount/update/reload의 Promise 완료만으로 성공을 판정하지 않는다. Contract·소스·CSS 오류는 reject 대신 getState().status === 'error'/onStateChange로 전달될 수 있다.
5. JSON 데이터 계약
현재 구현 타입 이름은 **OrgChartDataset**다. 과거 데이터 계약 문서의 초안 이름·선택 필드는 현재 타입보다 우선하지 않는다. JSON 문자열을 직접 JsonOrgChartDataSource에 전달하면 URL로 해석된다. 문자열 JSON은 먼저 JSON.parse(text)하고 Validator를 통과시킨다.
필수·선택 필드
- Dataset 필수:
schemaVersion: '0.1-draft',organizations: Organization[],employees: Employee[],affiliations: Affiliation[],metadata: Metadata. - Dataset 선택:
relations?: OrganizationRelation[],presentation?: PresentationConfig. - Organization 필수:
id: string,parentId: string | null,name: string,type: 'root' | 'division' | 'office' | 'div' | 'cell' | 'team',leaderId: string | null,displayOrder: number(0 이상 안전한 정수). 선택:companyId?: string. - Employee는 모든 필드 필수:
id,organizationId,affiliationId,name,position은 string;jobTitle,profileImage는 string 또는 null;workStatus는active | dispatched | onLeave. - Affiliation 필수:
id: string,name: string. 별도Company[]는 없다. Auto 회사 식별은 Organization.companyId와 등록된 회사 프리셋을 사용하며 직원의 관계사/법인 소속은 Employee.affiliationId로 구분한다. - Metadata 필수: 실제 달력 날짜인
asOfDate: 'YYYY-MM-DD', 비어 있지 않은dataVersion: string. 선택:schemaVersion?: '0.1-draft'(최상위와 같아야 함),updatedAt?: string | null(timezone 포함 RFC3339),updateNote?: string | null. - Relation은
id,sourceOrganizationId,targetOrganizationId,type: 'cooperation' | 'support'가 필수다. 추가 관계는 검증/보존되지만 기본 계층 연결선으로 렌더링되지 않는다. - Presentation을 넣으면
title: string,description: string | null,hubText: string | null,organizationOrder: string[],groups의showEmployees/showSummary/showDispatched/showOnLeave/showAffiliations: boolean모두를 넣는다. Presentation 생략은 현재 heading 제거 옵션이 아니다.
nullable 필드는 생략하거나 빈 문자열로 대신하지 않는다. 필수 string은 공백뿐인 값이 거절된다. Validator는 trim으로 원본을 고치지 않는다. Contract에 없는 키도 UNKNOWN_FIELD다. 원본에 추가 정보가 있다면 별도 자료에 보존하고 Contract용 변환본을 만든다.
최소 JSON: 회사 → 본부 → 팀 → 직원
{
"schemaVersion": "0.1-draft",
"organizations": [
{ "id": "company-root", "companyId": "saman", "parentId": null, "name": "삼안", "type": "root", "leaderId": null, "displayOrder": 0 },
{ "id": "hq-1", "parentId": "company-root", "name": "1본부", "type": "office", "leaderId": null, "displayOrder": 0 },
{ "id": "team-1", "parentId": "hq-1", "name": "1팀", "type": "team", "leaderId": "employee-1", "displayOrder": 0 }
],
"employees": [
{ "id": "employee-1", "organizationId": "team-1", "affiliationId": "saman", "name": "가상직원", "position": "차장", "jobTitle": "팀장", "profileImage": null, "workStatus": "active" }
],
"affiliations": [{ "id": "saman", "name": "삼안" }],
"metadata": { "asOfDate": "2026-10-13", "dataVersion": "minimal-1" }
}
leaderId는 해당 조직에 소속된 Employee.id를 참조한다. 위 팀장도 employees에 한 번만 있다. 조직장 별도 레코드를 추가해 총원을 두 번 세지 않는다. 직원은 한 organizationId로 직접 소속되며 이름으로 관계를 연결하지 않는다. ID 중복 검사는 collection 내부 기준이고 서로 다른 collection의 같은 문자열은 현재 Validator가 거절하지 않지만, 호스트 변환 단계에서는 엔티티 prefix로 구분하기를 권장한다.
profileImage는 HTTP(S) 또는 안전한 상대 URL이다. null 또는 이미지 load 실패 시 기존 사람 SVG Placeholder로 전환한다. credentials·data URL·javascript·protocol-relative URL을 이미지 API로 우회하지 않는다. 직위(position)와 직책(jobTitle)은 별개이며 연락처 같은 미지원 정보를 합성하지 않는다.
깊은 JSON: 회사 → 부문 → 본부 → 부서 → 팀 → 직원
현재 Auto의 트리는 enum 순서에 종속되지 않고 실제 parentId 관계를 따른다. 다음은 5개 조직 계층 + 직원 카드 예제다. 본부=office, 부서=div 대응은 이 예제의 변환 선택이며 일반 기업 의미를 강제하지 않는다.
{
"schemaVersion": "0.1-draft",
"organizations": [
{ "id": "deep-root", "companyId": "hanmac", "parentId": null, "name": "한맥기술", "type": "root", "leaderId": null, "displayOrder": 0 },
{ "id": "division-1", "parentId": "deep-root", "name": "기술부문", "type": "division", "leaderId": null, "displayOrder": 0 },
{ "id": "hq-1", "parentId": "division-1", "name": "기술본부", "type": "office", "leaderId": null, "displayOrder": 0 },
{ "id": "department-1", "parentId": "hq-1", "name": "설계부서", "type": "div", "leaderId": null, "displayOrder": 0 },
{ "id": "team-1", "parentId": "department-1", "name": "설계팀", "type": "team", "leaderId": null, "displayOrder": 0 }
],
"employees": [
{ "id": "deep-employee-1", "organizationId": "team-1", "affiliationId": "hanmac", "name": "가상직원", "position": "사원", "jobTitle": null, "profileImage": null, "workStatus": "active" }
],
"affiliations": [{ "id": "hanmac", "name": "한맥기술" }],
"metadata": { "asOfDate": "2026-10-13", "dataVersion": "deep-1" }
}
임의의 최대 Depth를 보장하는 API는 없다. 소스는 가변 계층을 처리하지만 극단적으로 깊거나 큰 입력의 렌더링·성능은 실제 브라우저에서 별도로 확인한다.
근거: 실제 타입, Validator, DataSource, 정규화, 집계.
6. Fixed / Auto 선택과 레이아웃
Fixed — 기존 Workdesk 디자인 구성
layoutMode: 'fixed'가 생략 시 기본이다. displayMode: 'interactive' | 'readOnly'와 별도 설정이다.
- Fixed readOnly는 기존 승인 SVG 카드·대표자·허브·요약과 Workdesk 구성/비례 축소를 사용한다. 기준 캔버스는 1920×914.5, 축 X=935다.
- 측정된 배치는
business-management/business-planning/talent-growth/integrated-system/design-planning/solution-salesID의 기존 열 구성에 맞는다. 상위 열이 매핑되지 않으면 일반 중앙 flow로 돌아가며 임의 데이터에 승인된 좌표 배치를 보장하지 않는다. 데이터의 ID/parentId를 디자인 매핑에 맞추려고 바꾸지 않는다. - readOnly는 전체 펼침이며 Fixed interactive는 기존 접기 동작을 제공한다. Auto용 TB/LR/Hybrid·회사 선택·Drag & Drop·Zoom/Pan Handle은 Fixed의 새로운 기능이 아니다.
- 표시 데이터 제약:
employee-card.ts는 Workdesk 표현 카드의 이름을홍 길 동으로 표시하고 Fixed 대표자 카드는대표이사직위를 표시하는 경로가 있다. 원본 Employee는 유지되지만 화면 표시가 원본 인명과 일치하지 않을 수 있다. 실제 사용자 데이터 표시에는 이를 보고하고 Auto를 검토한다. 이를 이번 문서 작업에서 수정하지 않는다. - readOnly가 제목·기준일·요약까지 제거하는 Chart Only prop은 아니다.
Auto / Expanded — 데이터 기반 가변 조직도
layoutMode: 'auto'는 회사별 독립 Root와 전체 관계를 사용한다. Auto의 회사별 컬러·접기·팝업·Zoom/Pan·이동은 Core 구현을 공유한다.
autoLayout.type:tb(기본, 위→아래),lr(왼쪽→오른쪽),hybrid(지정 Depth부터 세로 목록). 현재 기준 소스는 세 유형을 구현한다.hybridStartDepth?: 2 | 3 | 4: 기본 3, global Hybrid용. Root 조직=Depth 1이며 직원 카드를 조직 Depth로 세지 않는다.initialVisibleDepth?: 1 | 2 | 3 | 'all': 기본 all. 해당 Depth부터 그룹을 접는 초기 정책이고 조직장은 남는다. 값을 변경하면 접힘 상태를 초기 정책으로 재설정한다. 다른 레이아웃 변경은 유효한 ID의 접힘 상태를 유지한다.hybridOrganizationIds?: readonly string[]: TB에서 지정 조직의 자손만 세로 배치. LR/global Hybrid와 동시 사용은 거절한다. 전환 전에 빈 배열로 해제한다.interactions: { collapse?, zoom?, pan? }: 각각 기본 true, 서로 독립이다. Compact 레이아웃, 임의layoutConfig, 엔진 방향 옵션 전달, 공개 collapse setter는 없다.- 초기 자동 배치는 배율 1을 기본으로 실제 Bounds의 가로 중앙을 맞추며 상단 여유를 둔다. 항상 가로·세로 중앙 Fit하는 동작과 다르다. 사용자가 Pan/Zoom한 뒤에는 viewport를 보존한다.
- 명시적 Fit은 표시된 Bounds를 맞추고 가로·세로 중앙에 둔다. 큰 입력은 읽기 어려울 만큼 축소될 수 있어 initial Depth 또는 확대를 함께 검증한다. Fit은 수동 zoom.min보다 작아질 수 있다.
- Auto unknown 회사 프리셋은 거절한다. 회사 선택
undefined=전체,[]=명시적 빈 표시. 회사 숨김/복귀는 원본 데이터나 완료된 이동을 삭제하지 않는다. companyTopNodes는 회사 ID별employeeId/title/name/profileImage/organizationName/display표시 옵션이다. 기본은 Root.leaderId이며 직원은 해당 회사 Root 소속이어야 한다.employeeId: null은 상단 카드 배정을 제거한다.display는card | titleOnly | hidden. 표시 override는 원본 직원/팝업 identity를 바꾸지 않는다.
Vanilla Auto에서 설정 변경은 기존 handle로 한다.
import type { OrgChartHandle } from '@hm-design/org-chart';
export async function configureLayout(chart: OrgChartHandle) {
await chart.setAutoLayout({ hybridOrganizationIds: [] });
await chart.setAutoLayout({ type: 'lr', initialVisibleDepth: 2 });
await chart.setInteractions({ collapse: true, zoom: true, pan: true });
await chart.setSelectedCompanies(['hanmac', 'saman']);
// chart.setSelectedCompanies()는 전체, []는 빈 선택.
}
setter는 mount 후 Auto에서 호출한다. Vanilla의 생성 옵션인 layoutMode/displayMode/companyTopNodes/employeeConnections/zoom/organizationLinks를 바꾸려면 destroy 후 새 handle을 만든다.
React에서는 대응 prop으로 변경한다. dataSource 변경은 reload, autoLayout/interactions/dragDropEnabled/selectedCompanyIds와 Auto 팝업 변경은 기존 handle을 갱신한다. aggregationPolicy·CSS URL·layoutMode·displayMode·companyId·companyTopNodes·employeeConnections·zoom·organizationLinks 변경은 handle을 재생성하므로 객체를 안정적으로 유지한다. setAutoLayout는 patch merge이며 React에서 새 autoLayout 객체에 넣지 않은 필드가 매번 Core 기본으로 초기화된다고 가정하지 않는다.
근거: Core Handle/옵션, 배치 정책, 회사 검증, Workdesk 좌표, Fixed 구성, Viewport.
7. 디자인·회사별 컬러·카드 재사용
현재 Auto의 getCompanyColorPreset(id).primary 값:
hanmac한맥기술:#CB2E28saman삼안:#F08300jangheon-industry장헌산업:#4580A6jangheon장헌:#1D418Dptc피티씨:#84338Ehalla한라산업개발:#0068B7baron바론컨설턴트:#0C367F
색상은 조회 helper와 Core에 맡기고 새 카드 CSS에 복사하지 않는다. Root.companyId → 자손 상속 → legacy Root leader의 등록 affiliation → 생성 옵션 companyId(기본 hanmac)의 순으로 회사가 결정된다. 새 데이터는 Root.companyId를 명시한다. 조직 회사와 직원 affiliation은 다른 값일 수 있다. 카드 강조는 등록된 affiliation 프리셋을 우선하고 미등록 affiliation은 조직 회사 색을 사용한다. 자손의 companyId가 부모 회사와 다르면 Auto에서 COMPANY_HIERARCHY다.
Fixed는 별도의 기존 부서 ID별 스타일·SVG를 사용한다. 회사 컬러 옵션을 바꿔 Fixed 전체를 재색칠하는 API는 없다.
카드와 글자
- Fixed의 기존 대표자 SVG는 146×74, 1Depth 조직장 SVG는 146×68, 더 깊은 조직장/일반 직원의 small SVG는 108×52다. 등록 부서 자산이 없는 조직은 기존 fallback 카드다. 모든 Fixed 데이터에 SVG 매핑이 있는 것은 아니다.
- Fixed SVG 직위/이름 기본 크기는 small 15/18px, 대표자 20/24px, 부서장 SVG 직위 18px·이름 24px다. 이름 weight 600, 직위 400이며 전역 서체는 Noto Sans KR 등 기존 fallback을 따른다. 재설계 규격으로 사용하지 않는다.
- Auto는 SVG 배경 대신 기존 중립 카드와 회사 강조 Border를 쓴다. 일반 직원 폭 114px·최소 높이 54px, 조직장/상단 카드 폭 154px·최소 높이 72px, 사진 36×36, padding 8px, Border 1px, inset 강조 3px, 이름/직위 13/10px다. 긴 텍스트는 실측 높이가 늘 수 있다.
- Auto의 Root 대표자와 하위 조직장 카드는 같은 leader geometry를 사용한다. 1Depth/2Depth별 새 디자인 prop은 없다. 3글자 한글 이름은 표시 계층에서 띄어 쓰고 직위 공백을 표시상 제거하지만 원본과 callback 데이터는 유지한다.
- Auto 일반 카드에서 jobTitle·근무 상태를 별도 행으로 출력하지 않는 경로가 있다. 팝업은 원본 jobTitle/workStatus를 보여준다. Fixed의 파견/휴직 보조 카드·요약과 Auto의 직원 카드를 혼동하지 않는다.
- Fixed SVG의 외형 Border는 자산에 포함되고 Auto Border는 CSS/실제 연결면에 따라 강조한다. 회사 타이틀 텍스트는 현재 CSS에서 흰색이며 특정 색 조합의 대비가 승인됐다는 뜻은 아니다.
연결선·스타일 적용
Auto 전체 트리 방향은 autoLayout.type, 직원 연결 방향은 조직 ID별 employeeConnections: { [id]: 'top' | 'left' | 'right' }다. LR/세로 경로는 실제 endpoint를 측정해 연결면을 정하고 LR 직원 카드의 연결/강조면은 왼쪽이다. TB/Hybrid 조직장은 상단 연결을 사용한다. 직원 방향 옵션을 전체 트리 방향 API로 해석하지 않는다.
Auto 관계선/직원선은 기존 --hm-orgchart-expanded-line-color(기본 #738399)를 공유한다. Fixed 선·회사 Border와 별개다. 기본 선과 기존 SVG/회사 프리셋을 변경하지 않는다.
Core는 Shadow DOM을 사용하므로 호스트의 일반 selector로 내부 카드가 바뀌지 않는다. 기존 공개 CSS 두 개와 Core를 재사용한다. 별도 승인된 스타일 변경이 있을 때만 Expanded 스타일 안내의 실제 변수·스타일 로딩 경로를 확인한다. 변수 이름을 추측하거나 카드 교체 API를 만들지 않는다.
근거: 회사 프리셋, Fixed 부서 자산, 대표자 자산, 직원 카드, 공통 카드 CSS, Auto 카드 CSS, Auto 연결선.
8. 인터랙션·팝업·Drag & Drop
탐색
- Auto toolbar의 확대/축소는 현재 배율×step 또는 ÷step, 기본 step=1.2, min=0.05, max=3이다. 공개 handle에는 zoomIn/zoomOut이 없으며
zoomTo(scale)를 사용한다. - Ctrl/Command+휠 확대, viewport 초점에서 +/−, 빈 공간 드래그/방향키 Pan을 지원한다.
panBy(x,y),getViewportState(): {scale,x,y} | undefined를 제공한다. - toolbar의 100% 복원은 배율 1과 위치를 함께 복원한다.
zoomTo(1)는 배율 변경이므로 동일한 동작이 아니다. 공개 resetViewport 메서드는 없다. fitToView()는 Auto에서 전체 표시 범위를 맞춘다. Fixed의 위 handle viewport 호출은 Auto 동작을 제공하지 않는다.- 그룹 타이틀은 click/Enter/Space로 접기·펼치고 조직장은 남긴다. 부모 재확장 시 유효한 자손 접힘 상태를 유지한다.
interactions.collapse=false는 토글만 끈다. 하위 내용 없는 제목은 aria-disabled로 접기 불가이나 이동/키보드 대상일 수 있다. - Zoom/Pan을 끄면 해당 조작/API를 막지만 Fit은 독립적으로 가능하다. manual Pan이 OFF여도 키보드로 화면 밖 카드에 도달하면 배율을 유지하고 초점을 화면에 드러낸다.
팝업
Core/Vanilla 기본 직원 카드 click/Enter/Space는 native dialog를 연다. X·ESC로 닫고 Tab 초점을 가두며 원래 카드로 복귀한다. 카드가 사라졌거나 인스턴스가 정리되면 shell/연결 상태에 따라 복귀한다. Core dialog에는 외부 클릭으로 닫는 별도 핸들러가 없다.
React Auto의 employeePopup은 core(기본), hm-dialog, adapter, none이다. HM 모드는 /hm-dialog Entry 또는 명시적으로 주입한 HMEmployeeDialog를 사용한다. HM Dialog·Avatar·Button으로 X·닫기 버튼·ESC·외부 클릭으로 닫으며 onCloseAutoFocus에서 원래/재생성된 카드 또는 shell에 복귀한다. adapter는 호스트의 employeePopupAdapter를 명시적으로 선택한다. none은 기본 팝업/button 의미를 제거하지만 키보드 탐색용 초점은 남긴다. Fixed에서는 employeePopup 선택이 적용되지 않고 기존 Core 팝업을 유지한다.
onEmployeeClick(employee, context)를 지정하면 기본 팝업보다 우선한다. context는 organization, affiliation, trigger: HTMLElement, originalEvent: MouseEvent | KeyboardEvent다. 임의 popup API를 추가하지 않고 호스트가 자신이 만든 UI의 닫기·접근성·focus 복귀를 책임진다.
이동 정책
dragDropEnabled는 Core/React 공통으로 기본 false다. displayMode readOnly도 이 옵션을 대체하는 편집 잠금이 아니다.- OFF는 포인터 이동·Alt+M·handle.move를 차단하고
EDITING_DISABLED를 반환한다. OFF에서도 독립 설정에 따른 Pan/Zoom/Fit·팝업·접기는 유지한다. - ON은 Auto 직원 카드/조직 타이틀 드래그 이동, Alt+M → 방향키 대상 선택 → Enter 확정 / ESC 취소를 지원한다. 빈 공간은 Pan이다.
- Root, 자신/자손으로 조직 이동, 같은 소속, leaderId 참조를 손상시키는 조직장 이동 및 지정된 상단 직원 이동을 차단한다. 대상 회사 affiliation이 없으면 회사 간 이동도 거절한다.
- 동일 회사 직원 이동은 organizationId만 바꾸고 affiliationId를 보존한다. 회사 간 직원 이동은 affiliationId도 대상 회사로 변경한다.
- 그룹 이동은 parentId를 바꾸며 회사 간 이동은 하위 조직 전체 companyId와 소속 직원 affiliationId도 대상 회사로 바꾼다. ID·이름·직위·사진·자손 관계·조직장·정렬은 보존한다.
- 결과는 현재 Core 인스턴스의 새 immutable snapshot이다. 원본 JSON·DataSource·서버·파일은 수정하지 않고 영구 저장 API도 없다. 새 source/reload는 소스 데이터를 다시 적용한다. UI 회사 선택만으로 이동 snapshot을 초기화하지 않는다.
- OFF 전환은 진행 중 드래그/키보드 이동을 취소하고 이미 완료한 이동은 남긴다. 설정만 바꿀 때 reload하지 않는다.
import type { OrgChartHandle, OrgChartMoveResult } from '@hm-design/org-chart';
export async function moveEmployee(chart: OrgChartHandle): Promise<OrgChartMoveResult> {
chart.setDragDropEnabled(true);
const result = await chart.move({
kind: 'employee',
sourceId: 'hanmac-office-1-team-1-employee',
targetOrganizationId: 'hanmac-office-2-team-1',
});
chart.setDragDropEnabled(false);
if (result.moved) console.log(result.data); // 새 snapshot, 저장은 호스트 별도 책임
else console.error(result.code, result.message);
return result;
}
위 예제는 A를 mount한 handle 기준이다. onMove(result)도 성공/실패를 알리며 result.moved로 분기한다. 성공은 data/request/previousOrganizationId, 실패는 code/message다. React 성공 이동은 열린 HM Dialog를 닫는다. 데이터용 순수 applyOrgChartMove는 UI OFF 잠금과 별개인 변환 함수이며 handle.move 대신 호출해 편집 잠금을 우회하지 않는다.
근거: Viewport 이벤트, 편집 이벤트, 이동 정책, Core 팝업, HM 팝업, HM Dialog primitive.
9. 데이터 업데이트 — 예제 C
Vanilla update(input)는 전달 snapshot을 검증해 현재 인스턴스에 적용하고 DataSource 자체를 교체하지 않는다. reload(nextSource?)는 현재/새 source를 load한다. 이후 reload가 이전 source 값을 다시 적용할 수 있다는 차이를 구분한다. 인라인 JsonOrgChartDataSource는 생성 시 값을 캡처하므로 원본 객체를 나중에 바꾸는 방식은 업데이트가 아니다.
import {
JsonOrgChartDataSource, validateOrgChartDataset, type OrgChartHandle,
} from '@hm-design/org-chart';
export async function updateChart(chart: OrgChartHandle, input: unknown) {
const checked = validateOrgChartDataset(input);
if (!checked.valid) throw new Error(JSON.stringify(checked.issues));
await chart.update(checked.data);
if (chart.getState().status !== 'ready') throw new Error('업데이트 실패');
}
export async function replaceSource(chart: OrgChartHandle, input: unknown) {
const checked = validateOrgChartDataset(input);
if (!checked.valid) throw new Error(JSON.stringify(checked.issues));
await chart.reload(new JsonOrgChartDataSource(checked.data));
if (chart.getState().status !== 'ready') throw new Error('소스 변경 실패');
}
React는 dataSource identity가 바뀌면 같은 handle에 reload한다. useMemo/module 상수로 source를 안정적으로 만들고 매 render에서 새 source를 만들지 않는다. 다음은 호스트가 받은 새 데이터 prop으로 갱신하는 별도 React 예제다.
// UpdatingChart.tsx
import { useMemo } from 'react';
import { OrgChart } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource, type OrgChartDataset } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
const stylesheetUrls = { engine: engineUrl, core: coreUrl };
export function UpdatingChart({ dataset }: { dataset: OrgChartDataset }) {
const source = useMemo(() => new JsonOrgChartDataSource(dataset), [dataset]);
return <OrgChart dataSource={source} stylesheetUrls={stylesheetUrls}
layoutMode="auto" dragDropEnabled={false}
onStateChange={state => {
if (state.status === 'error') console.error(state.issues);
}}
onLifecycleError={error => console.error(error)} />;
}
새 dataset 객체를 전달한다. 성공한 onMove의 data를 집계용 read model로 저장하는 것과 바로 새 source로 넣어 reload하는 것을 구분한다. Core는 최신 요청 revision을 사용해 오래된 load 결과를 무시/취소한다. 재생성 옵션 변경은 viewport/이동의 보존을 보장하지 않는다.
10. AI 코딩 에이전트 적용 절차
- 현재 사용자 요청·Git 기준·공개 API를 확인하고 회사/조직 유형·부모 관계·분기 수·직원 수를 정리한다. Root를 포함한 조직 Depth와 직원 수 집계 기준을 구분한다.
- 자연어의 가상 데이터를 생성하거나 제공 JSON·CSV·Excel을 Contract로 변환한다. Core가 CSV/XLSX parser나 자연어 생성기를 제공하는 것은 아니다. 외부 변환 결과는 unknown으로 취급한다.
- 원본 이름·ID·소속·직위·사진·부모 관계를 보존한다. 조직장과 대표자를 총원에 포함할지 확인하고 employees에 한 번만 생성한다. 미지원 필드와 원본을 별도 보존하며 오류 항목을 삭제해 Validator를 통과시키지 않는다.
- validateOrgChartDataset와 요청별 회사·조직·Depth·인원 검사를 수행한다. Auto 회사 프리셋/회사 경계/상단 직원 제약도 확인한다. Validator 성공만으로 Auto 표시가 보장되지 않는다.
- 기존 공개 Core/React·카드·컬러·연결선을 재사용해 실제 client 진입점을 만든다. 새로운 엔진·입력 UI·개발 패널을 만들지 않는다.
- 구현 완료 후 한 번의 통합 검증으로 핵심 동작·데이터 보존·연결선·가독성·viewport·키보드·필요한 타입/build/Guard를 확인한다. 390/768/1280/1440px 및 요청 크기에서 확인한 실행 URL/캡처와 미검증을 보고한다.
예: 삼안 6본부, 팀 5·2·4·1·3·2=17, 총 500명은 실제 생성 데이터의 organizations와 unique employee 수로 검증한다. 한맥기술 3부문×2본부×4팀과 300명도 같은 절차로 처리한다. 이 가이드의 7명 예제 분배를 재사용하지 않는다.
공개 createOrgChartExampleDataset는 기존 template에서 employeeCount: 20 | 100 | 300, scope total | per-company로 가상 예시를 만드는 helper다. 임의 500명 생성·실제 Excel 변환 API가 아니다. 회사별 모드는 선택 회사마다 N명이며 total 모드는 원래 회사별 분배 후 선택 회사만 필터링한다. 실제 사용자 데이터를 이 helper로 재생성하지 않는다.
Chart Only 정책과 현재 기능의 관계
AI 페이지 생성 정책을 먼저 읽고 AGENTS.md·CLAUDE.md의 연결을 따른다. 별도 요청이 없다면 AI 생성 화면에는 조직도 캔버스만 표시하고 추가 제목·안내·테스트·JSON 패널을 만들지 않는다. 실제 적용 기준은 최종 통합된 정책이다.
P10.2에서 ChartOnly Worktree의 정책 보완을 통합했다. 기본 목표는 White Canvas + OrgChart Nodes + Connectors이며 외부 제목·기준일·요약·Zoom/Fit 버튼 바·설명·회색 배경은 요청 없이 추가하지 않는다. 정책은 생성 지침이며 새 Core prop/기능이 아니다. 현재 Auto는 heading/기준일·회사/인원 요약·Zoom/Fit toolbar·배율/조작 안내·Drag & Drop feedback를 내부 생성한다. Fixed readOnly도 heading·기준일과 기존 구성 요소를 유지한다.
현재 공개 옵션에는 chartOnly, showToolbar, showHeader 또는 모든 내장 부가 UI를 끄는 기능이 없다. displayMode readOnly나 presentation 생략으로 해결된다고 주장하지 않는다. createOrgChartTree는 기존 Fixed 계열 독립 Tree이며 Auto 기능 전체의 대체 API가 아니다. 요구한 최소 화면을 공개 API로 구현할 수 없으면 제약과 별도 승인 필요성을 보고하고 Core 복제·Shadow DOM 임의 조작·강제 숨김·병렬 정책 수정으로 해결하지 않는다.
P10.2의 독립 실제 tarball 브라우저 검사에서는 한맥기술 2본부×2팀·전체 20명 데이터를 React/Vanilla로 렌더링했다. 390/768/1280/1440px에서 흰색 내부 캔버스·7개 조직·20개 고유 직원 카드·TB 계층 연결선·Zoom/Pan과 콘솔 오류 0을 확인했다. 그러나 위 내장 UI와 rgb(244, 247, 251) 바깥 영역이 남아 Chart Only PARTIAL이다. Fixed readOnly도 제목·기준일을 유지한다. DOM 숨김·삭제·Core 수정은 사용하지 않았다.
Auto 초기 배치는 전체 Bounds의 가로 중앙·상단 16px 정렬이며 초기 자동 Fit과 다르다. Vanilla의 공개 fitToView()는 실제 전체 Bounds로 가로·세로 중앙 배치/축소를 수행한다. React Wrapper에는 공개 handle/ref나 초기 Fit prop이 없으므로 버튼 바 없이 호스트가 이 동작을 직접 요청하는 경로는 없다. 후속 개선 후보는 기본 동작을 유지하는 선택형 부가 UI 표시 옵션과 초기 Fit 정책이다. 이들은 현재 API가 아니며 이번 작업에서 추가하지 않았다. 자세한 근거와 미검증은 P10.2 통합 보고서를 참조한다.
React 없이 생성해 달라는 명시적 요청은 이 가이드의 Vanilla Core 경로로 처리하고 Runtime portability의 명시적 adapter 계약을 확인한다. HM React 화면을 HTML로 모방해 결과라고 제출하지 않는다.
11. 오류·검증·지원 한계
검사와 오류 처리
- validateOrgChartDataset는 valid=true이면 깊게 freeze한 snapshot을 반환한다. false이면
issues: {code,path,message}[]를 반환한다. - 기본 오류: SCHEMA_VERSION, UNKNOWN_FIELD, REQUIRED_VALUE, DUPLICATE_ID, EMPTY_ORGANIZATIONS, INVALID_TYPE/STATUS/RELATION, PARENT_REFERENCE, LEADER_REFERENCE/LEADER_APPROVAL_REQUIRED, ORGANIZATION_REFERENCE, AFFILIATION_REFERENCE, HIERARCHY_CYCLE, RELATION_REFERENCE, PRESENTATION_REFERENCE, UNSAFE_INPUT.
- organizations=[]는 오류이며 employees=[]는 유효할 수 있다. State 타입의 empty가 실제 empty UI 지원 증거는 아니다.
- Auto는 추가로 COMPANY_PRESET/COMPANY_HIERARCHY 및 TOP_NODE_CONFIG/TOP_NODE_REFERENCE를 검사한다. 기본 Validator는 회사 프리셋/회사 간 계층 경계를 검사하지 않으므로 두 층을 구분한다.
- source 오류는 SOURCE_URL/LOAD/PARSE/ABORT/SNAPSHOT, 스타일 오류는 CORE_STYLE 등으로 구별한다. Core state.error와 React onLifecycleError의 책임을 구분한다.
- calculateSummary(data).raw는 unique 직원·direct/inclusive·position·affiliation·status 집계다. 업무 승인 집계는 aggregationPolicy가 없으면 unavailable(policyRequired)이다. 현재 정책 구현은 includeLeaders=true와 membershipMode=primaryOnly 등 제한을 확인하며 임의 excludeLeaders/uniqueEmployee 지원을 선언하지 않는다.
주요 미지원·별도 확인 대상
- 자연어→데이터 자동 생성 UI, CSV/XLSX 직접 import, 서버 저장·외부 API·CSV/PDF/이미지 export는 공개 사용 기능이 아니다.
- 임의 회사 프리셋 등록 setter·카드 교체·Compact·공개 collapse setter·React imperative ref·Chart Only 전용 옵션은 없다.
- 제공 데이터의 최대 인원/Depth·모든 브라우저·실제 모바일 터치/스크린리더·외부 fresh install을 이번 소스 분석으로 보장하지 않는다.
- 기존 P10 PARTIAL/Guard FAIL 10건은 그대로다. 새 가이드의 문서 검사와 제품 전체 PASS를 혼동하지 않는다.
- P10.3 GuardReview는 별도 Worktree에서 작성한 원본 분석이다. 기존 FAIL 10건을 재현했으며 제품·Guard 수정은 없다. P10.3 Git 공유 단계에서도 이 FAIL을 유지한다. 완전한 Chart Only 공개 API, 외부 CSS 테마 표준화, Fixed SVG/PNG 테마화, 디자인 승인, main Merge와 Docker 배포는 별도 후속 작업이다.
이 가이드 예제 검증 범위
아래 검증 상태는 이 문서의 예제를 대상으로 한 별도 결과이며 기존 제품 회귀검사 결과가 아니다.
- TypeScript PASS: 이 문서에서 추출한 TS/TSX 코드 블록 8개를 설치된 TypeScript 7.0.2의 strict·ES2022·Bundler·react-jsx 설정으로 검사했다. Core/React import는 기준 Worktree의 src 공개 진입점, CSS URL은 기존 Vite client 타입에 연결했다. 새 빌드 산출물을 지원 증거로 사용하지 않았다.
- 데이터 실행 PASS: 기준 Worktree의 데이터 모듈을 임시 경로에 컴파일해 JSON 2개와 data.ts의 A/B 데이터 2개를 실제 Validator로 확인했다. A=7조직/7명, B=독립 Root 2개/10명, 조직장 중복 없는 집계, 직원 이동 후 원본 소속·총원 보존, 자손으로 그룹 이동 거절, 회사 프리셋 7개 값을 확인했다.
- Runtime NOT VERIFIED: App/UpdatingChart의 React 렌더링, mountChart의 Vanilla mount, configureLayout, handle 기반 moveEmployee/updateChart/replaceSource, HTML 진입점의 실제 실행은 확인하지 않았다. 타입 검사와 순수 applyOrgChartMove 실행을 이들 브라우저 예제의 실행 PASS로 해석하지 않는다.
- 실제 React/Vanilla 화면 렌더링·CSS load·인터랙션 시연·시각 QA: NOT VERIFIED.
- 설치/전체 build/Storybook 회귀·외부 소비 프로젝트 fresh install: NOT VERIFIED.
위 항목은 UsageGuide 원본 문서 작업 당시의 범위다. P10.2 추가 검증: 통합 후 TS/TSX 예제 10개를 현재 package 공개 .d.ts에 연결해 strict·skipLibCheck=false로 확인했다(PASS). 기존 실제 npm pack A/B/C 설치·타입·Build·팝업 검사 PASS는 현재 배포 JS/타입/CSS의 byte 동일성을 확인해 재사용했다. 독립 Chart Only React/Vanilla Build와 위 브라우저 검사는 새로 수행했다. 이 가이드의 HMChart/CustomChart 예제 자체의 브라우저 시연, 전체 회귀·성능·SSR·스크린리더·모바일 touch는 NOT VERIFIED다.
12. 관련 문서·실제 소스
필요한 부분만 확인한다.
- AGENTS.md, CLAUDE.md: 프로젝트 공통 제약.
- AI 생성 정책: 자연어/제공 데이터 생성·보존·최소 화면 정책.
- Expanded 사용법, Framework 통합, 독립 컴포넌트: 상세 소비 경로.
- Core 구현, 공개 옵션: 초기화·업데이트·정리·Auto setter·이벤트.
- React 구현, Props: lifecycle과 갱신 경계.
- 데이터 타입, 검증, 이동: 실제 Contract와 정책.
- P10 보고서: 기존 PARTIAL/Guard FAIL·별도 미검증 기록. 현재 기능의 근거는 보고서보다 이 가이드 기준 소스다.
원본 UsageGuide Worktree의 문서 예제 검증 기록은 위에 보존했다. P10.2에서는 이 가이드의 Entry/설치/Adapter 설명과 Chart Only 정책 연결을 보완한다. 제품 코드·기존 디자인·Core 공개 API를 추가 변경하지 않는다.