# @hm-design/org-chart v0.1 Framework-independent TypeScript/DOM Core backed by **dabeng orgchart 6.0.0**. No React runtime, wrapper, Storybook, navigation or API client. This version renders organizational hierarchy and employee/leader cards; additional relations are validated but are not drawn. P1 composes a separate top leader, presentation-only hub, hierarchy, position/affiliation summaries and dispatched/onLeave groups. The same `presentation.groups` fields hide each group independently. The hub does not create a fictitious organization or change `parentId`. ```js import { createOrgChart, JsonOrgChartDataSource } from '@hm-design/org-chart'; const chart = createOrgChart({ container: document.getElementById('chart'), dataSource: new JsonOrgChartDataSource(configuredJsonUrl), // Host/bundler resolves the two exported CSS assets to absolute URLs. stylesheetUrls: { engine: engineCssUrl, core: coreCssUrl }, onStateChange(state) { /* idle/loading/ready/error/destroyed */ }, }); await chart.mount(); await chart.update(nextDataset); await chart.reload(); chart.destroy(); ``` `displayMode` (optional, default `'interactive'`) selects the full composition presentation. `'readOnly'` is the view-only Workdesk layout: the hierarchy is fully expanded with no collapse buttons or diagnostic text (kind, ID, raw counts, vacancy, description), summaries use compact presenters of the same Summary (organization-by-position table, untitled affiliation bar and status groups), and a 1920 × 914.5 design canvas scales proportionally to the available container width and height. Standalone components ignore it. ## Organization links and employee information (P3) ### Layout boundary (P5-2 / P5-3) `layoutMode?: 'fixed' | 'auto'` is independent of `displayMode`. The default `'fixed'` keeps the existing Workdesk composition and its display-mode behavior. `'auto'` renders the complete validated hierarchy through dabeng 6.0.0. Root and same-parent siblings remain in their real hierarchy; the engine allocates recursive subtree widths. The Auto-only presenter reserves the tallest content height at each depth and observes article resizing so variable staff counts and font loading keep child rows and connectors aligned. Update/reload regenerate the same layout and dispose old observers. Auto has a transformed forest, initial Fit, 5–300% configurable Zoom, Ctrl/Command wheel Zoom, pointer Pan and keyboard Pan. Ordinary wheel scrolling remains with the page. Dialogs stay outside the forest. Set optional `Organization.companyId` on company roots; descendants inherit it. Child company changes are rejected instead of mixing independent trees. Legacy roots resolve from a known leader affiliation, then the `companyId` fallback (default `hanmac`). `companyId` is no longer a global recoloring selector. The seven IDs are `hanmac`, `saman`, `jangheon-industry`, `jangheon`, `ptc`, `halla`, and `baron`. Main colors style Expanded group headers and card accents; indexed secondary variables come from that same preset. Fixed ignores this option and preserves its organization gradients. Unknown Auto IDs reject construction with `TypeError`. The Auto presenter shares photo/position/name and employee dialog behavior without Fixed SVG cards, coordinates or scaling. Group title DOM carries the organization ID; the full title is a native toggle with aria-expanded and disabled empty groups. Separate link icons preserve page navigation. Child collapse state survives parent collapse and company reselection. Auto uses actual Employee names/positions; three-character Korean names display with spaces; position whitespace is removed only for display, preserving raw data. P5-4 retains the current Expanded cards by user decision; the two external SVG references are no longer required. Auto geometry variables live under `.hm-orgchart-expanded`; there is no new public `layoutConfig` API. The existing employee popup still uses its displayMode policy and receives raw data. P5-4 adds group-title Hover feedback and reveals offscreen keyboard-focused cards without changing Zoom. Drag click suppression does not block Enter/Space activation. See [Expanded usage and styling](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/docs/org-chart/expanded-usage.md) for complete Vanilla/React examples, Shadow DOM CSS ownership and verification boundaries. `selectedCompanyIds` / `setSelectedCompanies(ids?)` filter the view: undefined means all, [] means empty. Summary raw counts always describe the unmodified source; the Auto selection label reports selected company/employee counts separately. `getSelectedCompanies`, `zoomTo`, `panBy`, `fitToView`, `getViewportState` expose view operations. Selection requires a mounted Auto handle; Fixed rejects it. `companyTopNodes` maps company IDs to optional employeeId, title, name, profileImage, organizationName and display (`card`, `titleOnly`, `hidden`). An explicit employee must belong to that company's root; null removes top-card assignment. Overrides affect presentation only, preserving employee IDs, popup data and parentId. `employeeConnections` maps organization IDs to top/left/right staff routes in TB/Hybrid (default top). Their leaders connect from the title above; LR routes all employees from the left. SVG paths use actual article/card coordinates; each endpoint shares its direction with the corresponding single-side border accent. Employee preset follows its known affiliation, otherwise its tree. All remaining borders are neutral; an inset accent width variable preserves size. Width/height/padding/radius/photo/font/spacing variables are scoped to Auto. ```js const chart = createOrgChart({ ...configuredBaseOptions, layoutMode: 'auto', displayMode: 'readOnly', companyId: 'hanmac', selectedCompanyIds: ['hanmac', 'saman'], companyTopNodes: { hanmac: { title: '회장', display: 'card' } }, employeeConnections: { 'hanmac-office-0': 'left' }, }); ``` Layout mode/company/top-node/connection/zoom ranges are captured at construction. Auto `autoLayout`, `interactions`, company selection and the employee callback use their own setters on the existing handle. See the P7 matrix in the usage guide. Standalone Tree/Summary factories keep their existing rendering paths. `examples/org-chart/data-expanded-{small,medium,large}.json` contain exactly 20/100/300 unique virtual employees and 3/5/7 companies. Run `node examples/org-chart/create-expanded-fixtures.mjs` to reproduce them. After build/prepare, open `http://127.0.0.1:4178/expanded.html`. Expanded OrgChart is one of the same four public Stories; it offers fixture and company controls without filtering JSON in the Story. Native Core controls are the explicit framework-independent HTML adapter, not imitations of HM React UI. ### Virtual example selection (P7) `createOrgChartExampleDataset(template, selection)` is a pure data provider for both Vanilla and React. Supply the existing P5-3 **large** JSON fixture, which contains all seven company presets. The old Small/Medium/Large files and their historical 3/5/7-company policies remain unchanged; P7 selection does not require new JSON files. The helper validates and snapshots its input, reuses organization IDs and employee records, and adds deterministic virtual employees when a company needs more staff than the original fixture contains. ```js import { createOrgChartExampleDataset, JsonOrgChartDataSource } from '@hm-design/org-chart'; // template is the parsed examples/org-chart/data-expanded-large.json fixture. const allCompanyExample = createOrgChartExampleDataset(template, { scope: 'total', employeeCount: 100, }); // All seven companies together: exactly 100 unique employees. const twoCompanyExample = createOrgChartExampleDataset(template, { scope: 'per-company', employeeCount: 100, companyIds: ['hanmac', 'saman'], }); // Exactly 100 employees per company, 200 in this source. await chart.reload(new JsonOrgChartDataSource(twoCompanyExample)); ``` Supported scales are exactly `20 | 100 | 300`. Scope `'total'` allocates that count across all seven companies using the original fixture's unequal company proportions. An optional company subset then filters those allocations **without redistributing** them: a single selected company therefore displays only its share of the total example. Scope `'per-company'` gives every selected company the requested count, up to 2,100 employees for seven companies at 300 each. Omitting `companyIds` selects all seven; repeated/out-of-order IDs are normalized to preset order. All returned datasets are independent, deeply frozen snapshots. Leader IDs remain references to existing employees and are never counted twice. Missing sampled leaders are cleared while the complete, valid parent hierarchy is retained, including the original deeper and unequal branches. The helper rejects an empty `companyIds` list because the data contract requires organizations. To display no companies, retain a valid source and call `setSelectedCompanies([])` (or pass React `selectedCompanyIds={[]}`). Runtime layout, collapse state, employee popup policy, and actual API sources remain separate from example selection. The 2,100-person **data count** is distinct from rendering/heap performance; the earlier 300-person result does not verify it. ### Historical P5-1B verification and local runtime Core/React builds and Typechecks pass. Focused browser evidence covers the complete four-depth hierarchy, unequal employee counts, updates/reload, all seven company presets, shared HTML/React cards and popup focus, viewport overflow, and minimal Fixed baseline regression. Evidence is generated under `test-results/org-chart-p5-1b`; the authored runner is `examples/org-chart/verify-p5-1b.mjs`. Its baseline must be copied from a known pre-change build before rebuilding. The runner uses configurable `ORGCHART_STORY_ORIGIN`, `ORGCHART_PLAYWRIGHT_MODULE`, and `ORGCHART_CHROME`. Do not assume another PC's localhost or generated dist exists. Use npm with `package-lock.json`; a fresh install uses `npm ci`. The existing package test runner imports Node's `globSync`, so use a runtime providing that API, such as the verified Node 24, for focused unit tests. This PC's Node 20.20.1 built the packages successfully; the installed Codex Node 24.19.0 ran the unit tests without changing the runner. Final reference SVG design, collapse/Zoom/Pan, real screen-reader speech/touch and historical full regression suites are not verified by this phase. ### P3.2 follow-up: representative badges Representative cards never render job-title, role or status badges, empty attribute wrappers or a badge row, including standalone specimens. They display `대표이사` / `홍 길 동`, retaining the approved 146×74 asset and existing typography, with no gap between position and name. The fixture's raw representative position (`경영진`), employee data and dialog fields remain unchanged. Other 1Depth/2Depth badge policies are preserved. This display policy takes precedence over the historical P3.2/P3.1 notes below. ### P3.2 compact typography and card logos Large department cards use 24px names / 18px positions; compact cards use 18px names / 15px positions. Representative typography and the existing weights, line heights and colors are retained. Read-only dispatched/onLeave cards share the compact content layout (108×52 card, 24×44 photo slot), with their original gray surface and text colors. Status headers share the standard 2Depth 20px/700/19.7px typography; existing department-specific exceptions remain. Position and badge whitespace is removed only for display. Employee-card logo contain boxes scale exactly 1.5×: small 12→18px; representative/large/status 14→21px. Original assets and footer logos stay unchanged. See [P3.2 report](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/docs/org-chart/implementation-report-p3.2.md) for before/after browser measurements and preservation evidence. Historical P3.1 geometry below describes that earlier stage. ### P3.1 display policy and local font loading The OrgChart-scoped default family is **Noto Sans KR**; existing `--hm-orgchart-font-family` overrides remain available. The host must register its font-face once: HM Storybook already loads the existing HM Theme face. The HTML P1 host loads the unchanged `public/fonts/NotoSansKR-variable.ttf` copied to `assets/fonts/`; the React P1 host references that same source through its bundler. Global HM Theme files are unchanged. The Korean Core shell declares `lang="ko"` for consistent glyph shaping across host languages. Read-only Full Composition suppresses only the `경영진` **job-title badge**. Positions (including a literal `경영진` position), other raw badges, employee records, popup fields and statistics are retained. Position whitespace is removed only in the card display. Other renderer modes keep their badge policy. All card types share one rectangular neutral SVG for a missing or failed profile image; an available profile image takes precedence. Approved cards, photo-slot dimensions and P3 status-card geometry are unchanged. See [P3.1 report](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/docs/org-chart/implementation-report-p3.1.md) for focused browser evidence and limits. Optional `organizationLinks: Readonly>` maps existing organization IDs to host-configured URLs. Only original hierarchy depth 1 titles become native links. Missing, undefined or blank URLs keep ordinary titles; no destination is generated. HTTP(S) and relative URLs are accepted; invalid URLs, other schemes and credentials reject construction with `TypeError`. Group links retain native keyboard navigation and a visible focus outline, separately from employee activation. All employee cards, including the representative and dispatched/onLeave cards, support click, Enter and Space in both modes. By default they open a read-only native information dialog in the unscaled shell, outside the Stage. It shows only existing name, position, organization, job title, affiliation and work-status values; unavailable optional values are omitted. The readOnly fictitious display name matches the activated card (`홍 길 동`); underlying employee data stays unchanged. Close button, ESC, modal focus trapping and card focus restoration are provided. A name-overflow tooltip closes when information UI opens. To use a host-owned popup, provide the additive callback below. Supplying it replaces the built-in dialog; the host then owns popup accessibility and cleanup. The callback receives the unchanged Employee and resolved existing organization/affiliation, the focusable trigger card and original event. ```js const chart = createOrgChart({ ...configuredBaseOptions, organizationLinks: configuredOrganizationLinks, onEmployeeClick(employee, { organization, affiliation, trigger, originalEvent }) { openEmployeeInformation({ employee, organization, affiliation, trigger, originalEvent }); }, }); ``` Omit `onEmployeeClick` to use the default popup. Vanilla options are captured at construction; use a new handle to change the URL map or callback. There is no public Core `setOptions` method. `mount(container)` also accepts the container at mount time. Constructing a handle and importing the ESM facade require no DOM. Mount requires a connected HTMLElement. One handle owns one container, preserving host siblings. Duplicate mount and update/reload before mount or after destroy reject with a usage error. Data/Source failures become structured `error` states and resolve the operation. Destroy is synchronous, idempotent and terminal; use a new handle to mount again. `subscribe(listener)` returns an unsubscribe function; `getState()` returns the immutable current state. Consumer callback errors do not break the Core. `update` captures its input immediately and preserves the current Source. `reload(source?)` optionally replaces it. The last started operation wins; earlier requests are aborted, and stale responses cannot commit. Call completion means the operation was applied, superseded, or produced a controlled error, rather than that every image/font finished loading. Fixed keeps its prior regeneration policy. Auto preserves valid organization collapse IDs and transform state on toggles; reload/selection fits the new visible forest. Removed toggle focus is restored to the same organization, with viewport fallback. ## Data contract Public `OrgChartDataset`, `Organization`, `Employee`, `Affiliation`, `Metadata`, `PresentationConfig` and Source types are exported with declarations. Preserve documented names: `displayOrder`, `jobTitle`, `profileImage`, `workStatus`. The supported schema version remains **`0.1-draft`**; no silent migration is performed. The latest implementation request requires top-level `schemaVersion`; `metadata.schemaVersion` is an optional compatibility mirror and must match when supplied. `metadata.updatedAt/updateNote` and dataset `relations/presentation` are optional extensions. The original seven design documents are historical drafts and are preserved. Organization and employee IDs have independent namespaces. All references are checked; duplicate IDs within a collection, unknown fields, missing values, type errors, invalid dates/versions/statuses, parent cycles, unsafe markup/URLs and empty organizations produce `{valid:false, issues:[{code,path,message}]}` from `validateOrgChartDataset`. Zero employees is valid. Leaders outside their assigned organization require a future approved rule and are rejected with `LEADER_APPROVAL_REQUIRED`. Multiple roots remain separate trees, without a fabricated parent. Order is presentation order, then displayOrder, then code-unit ID order. `normalizeOrgChartDataset` and `calculateSummary` accept validated datasets. `calculateSummary` exposes `raw` counts for unique original employee IDs: total, direct/inclusive per organization, position, affiliation and status. These literal source counts are **not an approved business headcount**. No policy gives `aggregation.status='unavailable'`/`policyRequired`. A caller may provide an explicitly approved policy (`policyVersion`, `includedStatuses`, `activeStatuses`, `includeLeaders:true`, `membershipMode:'primaryOnly'`). Unsupported leader exclusion/multiple-membership rules or invalid policies return `unsupportedPolicy`. This release does not infer business meaning for dispatched/onLeave status. `JsonOrgChartDataSource` accepts an inline dataset or an HTTP(S)/relative URL, optionally `{fetch}`. Inline input is captured at construction; every load returns a fresh immutable snapshot. External HTTP/JSON errors are sanitized and distinguished by `OrgChartSourceError.code`. Source output is unknown until Core validation. Future API sources implement `load(options?:{signal?:AbortSignal}):Promise` and do not change Core APIs. URL paths are injected by the host, not embedded in Core. External JSON Fetch is an HTTP example; it is not supported as a file:// transport. ## Theme and CSS ownership P1 **ships an independent OrgChart design**. It requires no HM Theme, React/UI peer or font download. Resolve the two exported CSS assets to URLs; the Core loads them only into its dedicated open Shadow DOM. `style.css` includes its own scoped defaults and contains no private CSS imports. Summary-only components require only this CSS. `/browser` is a classic IIFE exposing `window.HMOrgChart`; `dist/index.js` is bundled ESM with no bare package imports. All colors, typography, spacing, borders, radius and gradients belong to `src/styles/design-tokens.css` and `src/core/style-presets.ts`. Public `--hm-orgchart-*` inputs inherit from the caller's container; internal `--hm-orgchart-resolved-*` aliases resolve defaults without overriding inherited inputs. HM Theme variables do not affect this design. Workdesk organization header gradients and white heading text are measured; neutral/card styling is a documented design adaptation, not an official Workdesk value. Existing v0.1 demos and historical validation records are preserved. P1 theme-free examples are `dist/p1.html` (HTTP/ESM) and `dist/p1-classic.html` (direct file/inline JSON). See [standalone component integration](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/docs/org-chart/standalone-components-p1.md). ## Engine disposal boundary Upstream provides no official destroy API. The facade clears its DOM, aborts requests and generation event signals, clears callback/data/engine references and disconnects the init observer through a narrow managed subclass hook. pan/zoom/minimap/drag/export are disabled to bound resource ownership; hierarchy expansion uses native buttons and engine show/hide methods. Transition listeners are removed with their discarded DOM. Browser verification records repeated lifecycle DOM, global event listener and active observer stability. It does **not** establish complete heap/GC recovery. The hook depends on pinned 6.0.0 internals and must be re-audited before an engine upgrade. ## Build and examples From the repository root: ```sh npm run typecheck:org-chart npm run test:org-chart npm run build:org-chart-example npm run dev:org-chart-example ``` Open `http://127.0.0.1:4178/index.html` for bundled ESM + external JSON. Open `examples/org-chart/dist/classic.html` directly for classic bundle + inline fictitious JSON. Both use package build outputs, never Spike code. Example controls demonstrate reload, employee movement/addition, destroy and recreation. Build artifacts are Git-ignored; source fixtures are checked in. `npm run test:org-chart-browser` expects Playwright supplied by the verification environment. If not installed locally, set `ORGCHART_PLAYWRIGHT_MODULE` to its absolute `index.mjs`/`index.js` and optionally `ORGCHART_CHROME` to a Chrome executable. Production has no browser-test dependency. Evidence is written under `test-results/org-chart-browser/`. The React wrapper owns only ref/effect lifecycle, source/props updates and cleanup, including forwarding lookup and Auto view options; HTML and React render the same Core. Standalone `createOrgChartTree`, `createPositionSummary`, `createAffiliationSummary` and `createStatusSummary` factories are exported at the package root and return the existing `OrgChartHandle`. The four public OrgChart Stories are Logo · Personnel Summary, Department Colors, Full Composition and Expanded OrgChart. Original verification Stories and independent component Stories remain available under the excluded `internal` tag. # P9.3 Auto 소속 변경 P9.4부터 `dragDropEnabled?: boolean`의 기본값은 `false`다. 아래 포인터·키보드· `handle.move()` 소속 변경은 `dragDropEnabled: true`일 때만 허용하며 OFF에서는 `EDITING_DISABLED` 결과를 반환한다. 순수 데이터 helper의 이동 검증·적용 계약은 유지한다. `handle.setDragDropEnabled(boolean)` / `handle.getDragDropEnabled()`로 같은 Auto 인스턴스를 갱신·조회한다. OFF 전환은 진행 중 이동과 강조·Pointer capture를 취소하고 완료된 이동·데이터·Zoom/Pan·접기 상태를 유지한다. Source 재요청·레이아웃 재생성은 없다. Fixed는 생성 옵션을 무시하며 setter는 Auto 전용 사용 오류를 반환한다. Expanded Auto는 빈 공간 Pan과 직원 카드·그룹 제목 Drag를 구분한다. 6px 이동부터 Drag를 시작하며 단순 클릭은 기존 정보 팝업·접기 동작이다. `Alt+M` → 방향키 → Enter로도 이동하며 Esc로 취소한다. Fixed에는 편집을 적용하지 않는다. `handle.move({ kind: 'employee' | 'organization', sourceId, targetOrganizationId })`는 포인터·키보드와 같은 검증 정책을 사용한다. `handle.getData()`는 현재 Auto의 동결된 데이터를 반환하며 `onMove(result)`로 성공(`moved: true`)·거절 결과를 받는다. 회사 간 직원 이동은 `organizationId`와 `affiliationId`를 대상 조직·회사로 변경한다. 회사 간 그룹 이동은 `parentId`, 전체 하위 조직의 `companyId`, 해당 조직 직원들의 `affiliationId`를 함께 변경한다. 같은 회사 내 이동은 기존 관계사 소속을 유지한다. 회사 Root·조직장 직원·자기 자신·자손으로의 이동은 제한한다. 성공 후 기존 엔진으로 재배치하며 대상 회사 프리셋과 새로운 조직/관계사 인원 집계를 적용한다. 데이터는 브라우저 인스턴스에만 유지한다. 회사 표시 선택은 이동 내용을 보존하고, 새 source의 reload/update는 새 데이터로 대체한다. JSON 파일·서버 저장은 수행하지 않는다. ## AI coding tools AI coding tools should read [`ai/AI_GUIDE.md`](./ai/AI_GUIDE.md) first (shared HMDesign entry point), then the OrgChart rules in [`ai/AI_USAGE.md`](./ai/AI_USAGE.md) (Chart Only / Full Page pre-selection, data rules), [`ai/COMPONENT_CATALOG.md`](./ai/COMPONENT_CATALOG.md) (Core and React API), [`ai/DESIGN_RULES.md`](./ai/DESIGN_RULES.md) and [`ai/COMPOSITION_PATTERNS.md`](./ai/COMPOSITION_PATTERNS.md). `ai/` is included in the packed package. To make a consumer project pick them up automatically, copy [`ai/AGENTS_SNIPPET.md`](./ai/AGENTS_SNIPPET.md) into its root `AGENTS.md`.