Files
HMDesign/docs/guidelines/runtime-portability.md
T

18 KiB

Runtime / AI Independent Portability Policy

이 문서는 HM Design System 결과물을 React 외 실행 형식으로 옮길 때 지켜야 하는 공통 계약이다. 특정 AI 모델, IDE, 유료 기능, 프레임워크 또는 빌드 도구를 전제로 하지 않는다.

1. Source of Truth와 Target Runtime

디자인 시스템의 기준은 다음 순서다.

  1. Design Token과 themes/*.json
  2. CSS Custom Properties와 Runtime Theme 계약
  3. Component visual state 및 interaction contract
  4. Component implementation
  5. Storybook/Preview

작업 시작 전에 Target Runtime을 기록한다. Source of Truth 우선순위는 themes/default.json/공개 Theme contract → Runtime-neutral Component/Pattern contract → CSS Custom Properties → Runtime adapter → Storybook/Preview 순서다.

  • REACT_VITE: 현재 React/Vite workspace와 @hm-design/ui 공개 API를 사용한다.
  • STATIC_HTML: HTML을 file://로 직접 열 수 있어야 한다. JSX/TSX/TypeScript, Vite 전용 기능, bare package import를 사용하지 않는다. 저장소 내 샘플은 승인된 상대 Theme artifact와 상대 font/compatibility CSS를 사용하며, file://에서 /src/... 절대 경로를 사용하지 않는다.
  • VANILLA_WEB: 일반 웹 서버 또는 정적 호스팅에서 HTML/CSS/JavaScript로 실행한다.
  • OTHER_RUNTIME: Vue, Svelte, Angular, Web Component 등으로 변환하더라도 HM Token·Theme·Component Contract를 유지한다.

Runtime이 달라져도 디자인을 재해석하거나 React 구현을 삭제하지 않는다. 기존 구현을 Reference로 삼아 같은 Design Contract와 Theme Contract를 소비하는 adapter/standalone implementation을 추가한다.

2. 실제 HM 계약과 파일 경로

다음 파일을 추측으로 복제하거나 별도 값으로 재작성하지 않는다.

  • themes/default.json: HM Default 컬러의 공식 원본
  • packages/ui/src/theme/contract.ts: 공개 Runtime Token, CSS property, 타입·검증 계약
  • packages/ui/src/theme/runtime.ts: Global/Scoped 적용·해제와 atomic validation
  • packages/ui/src/theme/loader.ts: URL fetch/resolve/load 경로
  • packages/ui/src/theme.css: package Theme CSS 진입점
  • packages/ui/src/theme/default.css, primitives.css, <component>.css: fallback, Semantic, Component Mapping
  • packages/ui/package.json: 공개 export인 @hm-design/ui/theme, @hm-design/ui/theme.css, @hm-design/ui/tailwind.css
  • docs/guidelines/runtime-theme-contract.md: Token별 CSS variable, 기본값, 영향 범위

React 소비 앱은 다음 순서로 HM CSS를 로드한다.

@import "tailwindcss";
@import "@hm-design/ui/tailwind.css";
@import "@hm-design/ui/theme.css";

Standalone 결과물은 승인된 Theme CSS artifact를 상대 경로로 연결하거나, 프로젝트가 생성한 standalone artifact를 포함한다. AI가 #... 색상, 임의 spacing/radius, Theme variable을 추측해 복제하지 않는다. 실제 변수명과 허용 입력은 Runtime 계약표를 기준으로 한다.

2.1 Tailwind v4 소비자 검색 범위

현재 저장소의 package.json과 lockfile은 tailwindcss / @tailwindcss/vite 4.3.3을 사용하며, Vite 설정은 react()와 tailwindcss() plugin을 연결한다. 앱은 src/index.css의 Tailwind/HM Tailwind와 src/hm-theme.css의 HM Theme를 main.tsx에서 로드한다. 외부 React 소비 앱도 CSS entry를 실제 bootstrap에서 import해야 한다.

Tailwind는 기본적으로 실행 cwd를 기준으로 자동 검색하고 node_modules, .gitignore 대상 등을 제외한다. 외부 프로젝트의 junction 또는 넓은 실행 루트에서 검색 지연이 발생하면 실제 CSS 위치·실행 cwd·연결 폴더를 확인한 뒤 앱 소스 범위만 지정한다. source("./")는 CSS 파일의 위치를 기준으로 자동 검색 기준 경로를 정하는 지원 구문이며, 모든 junction 지연을 해결한다고 보증하지 않는다. 아래는 소비 앱 CSS가 src/index.css이고 앱 코드가 src/에 있는 경우의 예다.

@import "tailwindcss" source("./");
@import "@hm-design/ui/tailwind.css";
@import "@hm-design/ui/theme.css";
/* Dashboard 상태를 표시하는 화면만 Theme 다음에 추가 */
@import "@hm-design/ui/dashboard-status.css";
@source "../index.html";

CSS entry가 루트에 있으면 같은 source("./")는 루트 전체를 뜻하므로 제한 효과가 다르다. src/ 밖 공유 UI가 있다면 그 실제 경로를 @source로 추가한다. 명시 목록으로 제한할 때는 source(none) + 필요한 @source도 지원하지만, 앱·공유 UI·HM source를 빠짐없이 지정해야 한다. 공식 Tailwind 소스 검색 문서.

HM Component 클래스 보존: 공개 @hm-design/ui/tailwind.css를 생략하지 않는다. 패키지 소스는 ./components / ./patterns를 검색하고, 기존 package build는 배포 CSS에서 이를 @source "./index.js"로 바꾼다. 따라서 앱 자동 검색을 src/로 좁혀도 HM compiled 클래스는 해당 CSS의 명시 source를 통해 검색한다. 소비자가 소스 alias로 직접 연결한다면 배포 CSS와 경로가 같다고 가정하지 말고 실제 source 파일 존재를 확인한다. HM package 전체나 외부 node_modules 전체를 추가 검색 대상으로 삼지 않는다.

완료 시 앱 CSS import 순서, HM @source 대상 존재, 필요한 utility 생성 여부(예: GNB 모바일 trigger의 lg:hidden, Badge의 flex·크기 class)를 실제 소비 빌드에서 확인한다. 이 문서 작업에서는 버전·설정·구문 지원·source 연결만 대조했다. junction 지연 재현과 외부 앱 utility 생성은 미검증이며, 새 설정 파일·의존성·빌드는 추가하지 않았다.

3. 변환 시 보존해야 하는 계약

layout, spacing, typography, border, radius, icon 크기·정렬, responsive behavior와 hover/focus/active/selected/disabled/checked/error/loading 상태를 유지한다. 접근성 semantic element, keyboard behavior, ARIA 속성도 Target Runtime에 맞는 방식으로 보존한다.

프레임워크 기능은 Target Runtime의 최소 구현으로 변환한다.

  • React state/props → 명시적 DOM state와 Vanilla JavaScript state
  • event handler → addEventListener
  • conditional className → class/attribute toggle
  • component composition → semantic HTML structure
  • package icon → 배포 가능한 SVG 또는 허용된 icon artifact

단순 layout·typography·color·responsive 표현은 JavaScript에 의존하지 않는다. JavaScript는 accordion, dropdown, modal, tabs 등 실제 interaction에만 추가한다.

3.1 Dashboard 상태 계약

Dashboard 업무 상태의 Runtime-neutral 기준은 packages/ui/src/patterns/dashboard-status/contract.ts다. 별도 Runtime은 다음을 동일하게 유지한다.

  • 상태 ID: active, hold, done; 필터 ID: all, active, hold, done, issue
  • issue는 독립 Boolean flag이며 상태 값과 동시에 존재할 수 있음
  • done label은 완료
  • all은 모든 record, issue는 issue === true, 나머지는 record.status === filter로 매칭
  • 상태 표시에는 data-status, 이슈에는 data-issue="true", 필터 컨트롤에는 data-filter, 선택에는 aria-pressed="true|false" 사용
  • CSS 원본은 packages/ui/src/patterns/dashboard-status/dashboard-status.css이며, 패키지 소비자는 @hm-design/ui/theme.css 다음에 @hm-design/ui/dashboard-status.css를 로드한다. 기존 src/patterns/dashboard-status-contract.css는 같은 원본을 import하는 호환 경로다. 상태 CSS는 Custom Property만 정의하므로 Badge와 필터 숫자 등의 색상 mapping은 Runtime adapter에서 연결하며 색상 literal을 복제하지 않는다. active/hold/issue 값은 기존 Story 시각 보존용 compatibility mapping이며 HM Theme JSON을 변경하지 않는다. React/HTML의 done은 모두 --hm-dashboard-status-done을 사용하고, :root 및 [data-hm-theme]에서 해당 scope의 HM white token으로 재계산한다. 적용 순서와 Badge mapping.

React는 이 계약을 Badge와 Button/기존 FilterBar 조합으로 표현한다. Static HTML은 같은 attribute를 가진 semantic button/span과 addEventListener 기반 필터링으로 표현하며, aria-controls와 결과 count의 갱신도 유지한다. selected, hover, focus-visible, disabled는 CSS와 native button semantics로 보존한다.

3.2 Static HTML 실행 체크리스트

  1. docs/patterns/dashboard-status-static.html을 브라우저에서 직접 file:///...로 연다.
  2. ../../packages/ui/dist/theme.css, ../../src/patterns/dashboard-status-contract.css, ../../public/fonts/NotoSansKR-variable.ttf가 모두 로드되는지 확인한다.
  3. 전체/진행/보류/완료/이슈 버튼의 aria-pressed와 결과 count를 확인한다.
  4. 이슈 record가 상태 Badge와 독립 이슈 flag를 동시에 표시하는지 확인한다.
  5. 결과가 0건인 필터에서 Empty State와 aria-live 갱신을 확인한다.
  6. DevTools Console에 오류가 없는지 확인한다. 자동화 환경이 file://를 차단하면 PASS로 기록하지 않고 미검증으로 남긴다.

3.3 AI 독립 재현 체크리스트

이전 대화 없이도 다음 파일만으로 재현 가능해야 한다: AGENTS.md, packages/ui/src/patterns/dashboard-status/contract.ts, src/patterns/dashboard-status-contract.css, src/patterns/dashboard-layout.stories.tsx, docs/patterns/dashboard-status-static.html, packages/ui/dist/theme.css, docs/guidelines/runtime-theme-contract.md. 구현 후 상태 ID/label, matching/count, DOM attributes, Theme artifact 상대 경로, React/Static HTML 차이, file:// 검증 결과를 각각 보고한다.

3.2 Button 사용 계약

Button의 공통 기준은 Button 문서의 역할·조합 규칙을 따른다. brand는 실제 화면당 최대 1개이고 팝업에는 사용하지 않으며, 일반 확인/작성은 같은 size의 Solid 세트, 삭제 확인과 반복·보조 작업은 같은 size의 Outline 세트를 사용한다. Static HTML과 다른 프레임워크는 React API를 복제하지 않아도 되지만, semantic button, data-slot/data-variant/data-appearance/data-size 계약, 기존 Theme CSS variable, native disabled 및 keyboard/modal 동작을 보존해야 한다.

4. Asset과 실행 위치

STATIC_HTML은 Vite asset resolution을 사용하지 않는다. HTML과 함께 이동 가능한 상대 경로를 사용한다.

./assets/
./styles/
./fonts/
./icons/

CSS, JavaScript, SVG/image/icon, font, theme stylesheet를 모두 확인한다. package의 compiled CSS가 사용하는 font 경로와 source 개발 서버의 경로가 다를 수 있으므로, package 산출물 사용 시 package dist와 export 계약을 우선한다. 현재 source packages/ui/src/theme.css의 개발용 font 경로는 /fonts/NotoSansKR-variable.ttf이며 App/Storybook은 public/fonts/를 사용한다.

4.1 Noto Sans KR 경로: 실행 환경별 확인

  • HMDesignSystem 개발 저장소: 원본은 public/fonts/NotoSansKR-variable.ttf, 라이선스는 같은 폴더의 NotoSansKR-OFL.txt다. Theme source의 @font-face는 /fonts/NotoSansKR-variable.ttf를 사용한다. Vite는 public/을 루트에서 제공하고 Storybook은 .storybook/main.ts의 staticDirs로 /fonts에 연결한다. 이 경로는 해당 서버 환경의 계약이다.
  • 포터블 테스트 ZIP(현재 기준 r3, 아래는 r2 시점 확인 기록): r2 ZIP 안 HMDesignTest/public/fonts/, HMDesignTest/packages/ui/dist/fonts/, HMDesignTest/storybook-static/fonts/에 기존 폰트와 라이선스가 모두 있다. 포터블 실행기는 사전 빌드된 Storybook을 제공하므로 실행 화면은 그 안의 font/CSS asset을 사용한다. 개발 폴더의 /fonts 경로를 다른 실행 위치에 그대로 적용한다고 가정하지 않는다. r2는 문서 개선 이전 과거 스냅샷으로 보존하며 현재 기준이 아니다. r3의 폰트 포함 여부는 r3 Inventory로 별도 확인한다.
  • 별도 React 소비자: @hm-design/ui/theme.css는 공개 export인 dist/theme.css다. 기존 build가 원본 font-face URL을 ./fonts/NotoSansKR-variable.ttf로 바꾸고 폰트·OFL 파일을 dist/fonts/로 복사한다. 정상 package를 CSS import로 소비할 때 별도 앱 /fonts 복사에 의존하지 않는다. Vite가 CSS url() 자산을 처리하므로 최종 배포 base와 출력 URL을 확인한다. Theme 소스 파일만 복사하거나 CSS만 분리하면 이 계약이 사라지므로 실제 폰트와 상대 경로도 함께 배치한다. Vite 공식 Asset 문서.
  • 단일 preview.html: file://에서는 /fonts/...가 HTML 옆 폴더를 뜻하지 않는다. 파일과 폴더를 함께 전달한다면 상대 CSS/font URL을 사용하고, CSS 안 URL은 HTML이 아닌 해당 CSS 파일 위치 기준으로 계산한다. 기존 Static HTML은 상대 package Theme와 별도 상대 font-face를 사용하는 다중 파일 Reference다. HTML 하나만 전달하려면 승인된 Theme·소비 CSS의 import를 모두 펼쳐 inline하고 폰트·아이콘·이미지 등 외부 자산도 내장한다. Tailwind 입력 CSS의 @source / @apply나 bare package import를 브라우저에 그대로 전달하지 않는다.

단일 HTML의 font-face는 기존 TTF를 data URL로 내장할 수 있다. 아래는 저장소 루트에서 기존 폰트로 삽입할 CSS 문자열만 만드는 PowerShell 예이며, Theme/다른 Asset의 내장을 대신하지 않는다.

$fontPath = Join-Path (Get-Location) 'public/fonts/NotoSansKR-variable.ttf'
$fontData = [Convert]::ToBase64String([System.IO.File]::ReadAllBytes($fontPath))
$fontRule = '@font-face { font-family: "Noto Sans KR"; src: url("data:font/ttf;base64,' + $fontData + '") format("truetype"); font-style: normal; font-weight: 100 900; font-display: swap; }'
# 기존 font-face를 $fontRule로 교체해 preview.html의 <style>에 삽입한다.

Font Family Noto Sans KR, 기존 variable weight 100 900, 폰트 파일·라이선스를 유지한다. 폴더 배포에는 OFL을 함께 전달하고 단일 HTML 배포에는 기존 OFL 전문을 HTML comment 등으로 포함한다. base64는 파일 크기를 늘리므로 내장 여부는 전달 형식에 따라 선택한다. 최종 font 요청·fallback 여부·다른 Asset 로드와 file:// 실행은 소비 결과물에서 검증하며, 이번 문서 작업에서 새 HTML 생성·실행은 하지 않았다.

5. Target Runtime별 최소 검증

5.1 React Dashboard standalone export

설치 없는 테스트 ZIP은 사전 빌드된 Storybook의 Dashboard 대표 Story를 제공한다. 독립 Dashboard export는 기존 배포 필수 구성이 아니며, STEP 5에서 UI Preview로 표시된 미검증 dist-dashboard/는 포터블 ZIP에서 제외한다. 아래는 개발자용 별도 export 계약이며 해당 빌드 결과의 성공을 보증하지 않는다. 포터블 테스트 안내. 이 dist-dashboard/ export는 H-1(독립 단일 HTML 미리보기 POC, PASS)의 성공 산출물과 동일하지 않다. H-2(AI 생성 화면의 자동 빌드·캡처·검증)는 Storybook 배포와 별도 기능이며 구현 완료 여부를 이 문서가 보증하지 않는다.

Dashboard Production 화면은 Storybook Story를 import하지 않는 독립 React composition을 소비하는 Vite entry로 생성한다. Storybook은 구조·상태의 Reference이며 Production export의 CSS 또는 bootstrap source가 아니다.

  • Source: src/patterns/dashboard-composition.tsx의 DashboardScreen
  • Entry: src/exports/dashboard/index.html + src/exports/dashboard/main.tsx
  • Build: npm run build:dashboard-export
  • Output: dist-dashboard/
  • CSS entry 순서: @/index.css → @/hm-theme.css → @hm-design/ui/dashboard-status.css → ./dashboard-export.css. @/index.css가 Tailwind 본체와 HM Tailwind CSS를 로드하므로 @hm-design/ui/tailwind.css만 단독 import하지 않는다.
  • Runtime: React compiled IIFE bundle, classic defer script, dynamic import disabled, relative asset base

이 export는 Storybook manager·Story CSS·개발 서버에 의존하지 않는다. DashboardScreen은 Dashboard Production Composition을 직접 사용하며, dist-dashboard/index.html은 HTTP 정적 서버와 file:// 직접 실행을 각각 별도로 검증한다. HTTP 성공은 file:// 성공을 의미하지 않는다. 빌드 성공 또는 CSS 텍스트의 선택자 존재만으로 화면 검증을 통과시키지 말고, 실제 렌더링에서 .flex·.grid 등의 utility, Theme CSS Variables, Dashboard Status CSS 및 Header/Layout/Card/FilterBar 레이아웃 적용을 확인한다.

  • REACT_VITE: Vite 실행, Theme 적용, component rendering, interaction, responsive behavior를 확인한다.
  • STATIC_HTML: 실제 HTML 파일을 file://로 직접 열어 CSS/JavaScript/font/SVG/image/icon 로드, HM CSS Custom Properties, responsive behavior, interaction, console runtime error를 확인한다. Vite dev server 성공만으로 완료 처리하지 않는다.
  • VANILLA_WEB/OTHER_RUNTIME: 실제 실행 환경에서 Theme Contract, visual/state/interaction/responsive parity와 asset resolution을 확인한다.

검증은 전체 회귀보다 변환된 범위의 최소 실행 검증을 우선한다. 확인이 불가능한 항목은 성공으로 간주하지 말고 차이와 제한을 기록한다.

6. 구현 및 AI 독립성

작업에 필요한 정보는 저장소의 코드, Theme artifact, 문서, package export와 Reference Story에 있어야 한다. 이전 AI 대화, Memory, 특정 IDE/Agent 전용 기능을 전제로 하지 않는다. 구현 편의를 위해 Theme이나 Component visual contract를 변경하지 않는다.

우선순위는 HM Theme/Token 정확성 → visual parity → state parity → responsive behavior → accessibility → interaction → runtime 편의성이다.

관련 문서: Runtime Theme, Runtime 계약표, Theme delivery, Theme/Token 가이드, 개발 적용 가이드.