Files
HMDesign/docs/guidelines/개발-적용-가이드.md
T
shkim4 89011a2e42
Deploy Storybook / deploy (push) Successful in 1m12s
docs: standardize Git submodule and workspace installation
2026-10-23 11:06:19 +09:00

150 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 개발 적용 가이드 (검토용 개정안)
> 기준 우선순위: 2026-10-10 추가 수정 요청의 확정 사항 → 충돌하지 않는 D-01/D-02 → 현재 HM Component/Pattern/Theme → 현재 Storybook/구현 → 과거 문서. [현재 추가 수정 기준](publisher-additional-review-2026-10-10.md). 미확정 값은 임의 확정하지 않으며 과거 검증 기록은 보존합니다.
페이지 구현 전에 [Navigation Single Mode Policy](navigation-single-mode.md)를 적용한다. AI 코드 생성·제안, 신규/기존 화면, 프로토타입, Production 모두 GNB / LNB / DASHBOARD 중 정확히 하나만 선택한다. 모바일도 같은 체계의 Responsive Variant를 사용하며 별도 요청 없이 Navigation 조합을 생성하거나 제안하지 않는다.
> 대상: 디자인팀과 서비스 개발팀. 현재 UI/CENTER compiled dist build/export, JSON Default/서비스 partial, 공개 Runtime 검증·Global/Scoped Apply와 URL Loader, Storybook JSON Preview, Theme-only artifact 산출이 구현되었다. 현재 테스트 공유는 Git Clone → npm ci → npm run build:packages → npm run storybook(6024)이다. [현재 Git 설치·실행 안내](../../README.md#git-clone-설치실행-현재-테스트-공유-기준)를 먼저 따른다. HM 패키지는 npm Registry로 배포하지 않으며 실제 CDN infrastructure/publish pipeline은 다음 단계다. Git Clone은 로컬 workspace를 빌드하고 다른 프로젝트는 [Git Submodule + npm Workspace](external-install-submodule.md)로 연결하므로 HM Registry 접근·publish가 필요하지 않다. 아래 장기 운영 예시의 `@company/ui`는 목표명이며 현재 import는 `@hm-design/ui`다. 현재 실제 API/독립 배포 절차는 [Theme delivery](theme-delivery.md), 디자인팀 workflow는 [Theme guide](designer-theme-guide.md)를 따른다.
## 1. 착수 순서
AI 신규·수정 UI 작업은 사용자의 별도 요청 없이 기존 Component / Storybook Reference 우선, Theme / Semantic 기본 적용, 단일 Navigation, 최소 Custom을 적용한다. 변경 범위(`STORYBOOK_ONLY | PRODUCTION_UI | BOTH`)를 정한 뒤 [AI 신규 화면 구현 순서](shadcn-기반-구현-원칙.md#9-ai-신규-화면-구현-순서)를 따른다. Theme 사용과 Theme Source 수정은 [테마 토큰 가이드](테마-토큰-가이드.md)의 별도 조건을 적용한다.
### 1.1 패키지·운영 기반 도입 순서
아래는 서비스 도입 계획이며 이미 구현된 HM Component를 새로 생성하라는 순서가 아니다.
1. 서비스별 사용 현황을 모아 공통화할 컴포넌트 3~5개와 필요한 Semantic Token을 선정한다. 예: Button, Input, Card, Dialog, Select.
2. `@company/ui`의 공개 API, 기본 Theme CSS, Semantic Token 계약을 버전 관리한다. 이름·타입·기본값·허용 범위를 한곳에서 정의한다.
3. shadcn 컴포넌트는 Design System 저장소에서만 도입한다. upstream 버전/도입일과 변경 이유를 기록하고 회사 API·토큰·접근성 기준에 맞게 수정한다. 이후 upstream 변경도 diff를 검토해 선택적으로 반영한다.
4. Storybook이 패키지의 **공개 진입점**을 사용하게 하고 대표 상태 및 Theme Preview를 추가한다.
5. 한 앱에서 패키지와 기본 CSS를 설치한다. 앱 번들에 import하지 않은 별도 로컬 서버의 Theme JSON을 Loader로 읽어 값만 바꾸고, 앱 재빌드 없이 다음 진입·새로고침에서 반영되는지 확인한다. 운영 단계에서는 이를 API/CDN Publish 경로로 바꾼다.
서비스 앱에서 `shadcn add`로 컴포넌트를 별도 생성하거나 공통 컴포넌트 코드를 복사해 고치지 않는다. 서비스 전용 조합은 공개 컴포넌트를 감싸서 만든다. 공통 API 변경이 필요하면 Design System 변경으로 제안한다.
## 2. 단일 저장소의 파일 책임과 패키지 경계
현재 파일 배치와 목표 배치를 구분한다. **한 Git 저장소**에서 코드, Storybook, Theme 초안을 함께 버전 관리할 수 있으며, 별도 Git 저장소나 monorepo 구성은 필수가 아니다.
| 위치 | 현재/목표 | 책임과 배포 경계 |
|---|---|---|
| `packages/ui/src/components/ui/`, `src/hm-theme.css` | 현재 구현 | Production Component와 Demo 스타일 분리. `@hm-design/ui` compiled dist export (JS·타입·CSS) |
| `.storybook/`, `src/components/*.stories.tsx` | 현재 시제품 → 계속 사용 | 실제 컴포넌트 검수. 패키지 산출물에는 Story를 넣지 않음 |
| `packages/ui/src/patterns/`, `packages/ui/src/theme.css` | 현재 구현 | 업무/Layout composition과 기본 Theme CSS. Library 산출물은 `npm run build:packages`로 생성 |
| `packages/center-graphic/` | 현재 구현 | `@hm-design/center-graphic` 독립 package 배포 경계. UI package와 필수 의존 없음 |
| `packages/ui/src/theme/contract.ts`, `runtime.ts` | 현재 구현 | 공개 94개 입력(색상 92개 + radius 2개), 형식/범위 검증, atomic fallback·cleanup, Global/Scoped 적용. `@hm-design/ui/theme` export |
| `themes/drafts/` | Runtime MVP 제안 | 디자인팀의 Theme 값 초안·검수 이력. Git 커밋만으로 운영 Theme이 바뀌지 않으며 패키지에도 넣지 않음 |
| `themes/*.json` | 현재 구현 | `themes/default.json`은 공식 Default Color Source of Truth. 검증된 top-level JSON은 Theme CDN 배포 단위이며 Git push와 CDN publish는 별도 단계 |
| 서비스 앱의 bootstrap | 개발팀 앱 | 패키지 버전 설치, themeId/mode 결정, API/CDN fetch·캐시·실패 처리, 검증된 값 적용 |
집계 기준 Commit은 `3ebc84e32cf91298a2a4d8046753244c499fd719`다. `scripts/theme-contract.mjs`가 `contract.ts`의 공개 Key·Property·type 항목을 추출한 `definitions.length`는 **94개(색상 92개 + radius 2개)**다. `Object.keys(themes/default.json의 tokens).length`는 색상 직접 설정값 **92개**이며 radius는 JSON에 없고 CSS Template에서 기본값을 제공한다. `scripts/theme-defaults.mjs --check`는 JSON + `scripts/theme-templates/{primitives,default}.css` → `packages/ui/src/theme/{primitives,default}.css` 및 `default-values.generated.ts`의 **94개 계약 기본값** 연결을 확인한다. 공개 CSS의 계산·Component 매핑 변수 선언 수는 이 Runtime 입력 수와 별도 집계다.
패키지 공개 파일은 `package.json`의 export·포함 목록과 빌드 결과로 확인한다. 기본 CSS와 컴포넌트 CSS 규칙은 패키지 버전에 묶인다. 외부 발행 대상은 계약에 등록된 **기존 Semantic Token 값**이며, 변경된 JSON/CSS 초안을 자동으로 운영 앱에 노출하지 않는다. 필요해지면 `packages/ui`와 `apps/storybook`으로 재배치해도 Git 저장소는 하나로 유지한다.
검증 계약은 Storybook·서비스 앱·Theme Publish가 공유해야 한다. 현재 검증·적용 도우미는 `@hm-design/ui/theme`에서 공개한다. 네트워크 URL, tenant 식별, 인증, 갱신 시점은 개발팀이 서비스 앱에서 관리한다.
## 3. Token 연결과 기본값
현재 연결은 Primitive → Semantic → Component Mapping이다. 다음은 실제 HM selector와 Token 이름을 사용하는 예다. 컬러 원본은 `themes/default.json`이며 생성 CSS를 별도 수동 원본으로 관리하지 않는다. 전체 입력은 [Runtime 계약표](runtime-theme-contract.md)를 따른다.
```css
/* 앱 초기 렌더 전에 로드 */
@import "tailwindcss";
@import "@hm-design/ui/tailwind.css";
@import "@hm-design/ui/theme.css";
/* package의 Input Component Mapping이 Semantic을 소비하는 예 */
:root, [data-hm-theme] {
--hm-input-border-color: var(--hm-sem-stroke-input-default);
}
[data-slot="input"] {
border-color: var(--hm-input-border-color);
}
```
Tailwind `bg-primary`를 사용한다면 package의 `tailwind.css`에서 `--color-primary` → `--primary` → `--hm-sem-action-primary` 연결을 확인한다. 클래스 이름이 같다는 이유만으로 Theme 연동이 성립하지 않는다. 컴포넌트 코드의 임의 hex/px 값, Primitive 직접 참조, 서비스별 `!important` override는 허용하지 않는다. 구조적 고정값은 Runtime 계약 밖의 코드 값으로 별도 관리할 수 있다.
## 4. Theme 문서와 버전 호환성
MVP의 Theme 문서 예시는 다음과 같다. `tokens`는 **선택적 override**이므로 없는 키는 기본 CSS를 사용한다. 현재 Light/Dark 구분이 없어 모드별 구조는 두지 않는다.
```json
{
"schemaVersion": 1,
"themeId": "default",
"themeVersion": "2026-10-02.1",
"tokens": {
"color.action.primary": "#7c3aed",
"radius.input.default": "6px"
}
}
```
- `schemaVersion`은 필드 구조와 Token 계약의 호환성을 판단한다. 앱이 지원하지 않는 버전이면 Theme 전체를 적용하지 않는다.
- `themeVersion`은 Publish할 때마다 새로 발행하는 불변 값이다. `@company/ui` 패키지 버전과 독립적이다.
- 구버전 앱이 남아 있는 동안에는 해당 앱의 계약에서 지원하지 않는 Token을 운영 Theme에 발행하지 않는다. 계약 확장은 패키지 릴리스와 앱 업데이트가 먼저다.
현재 HM Storybook 시제품은 브랜드 배경과 주요 버튼의 면·테두리 그라데이션을 역할별 stop 색상으로 조합한다. 초안 JSON에는 allowlist에 등록한 stop 색만 6자리 HEX로 적고, 배경·버튼 면·버튼 테두리 각각의 stop 세트는 부분 적용하지 않는다. 각도·stop 위치·그림자는 기본 CSS 규칙으로 유지한다. `radius.input.default`와 `radius.container.default`는 별도 Semantic 역할로 연결하고 2–12px 정수를 공통 Runtime API에서 허용한다. Placeholder는 확인된 `color.fg.placeholder` 기본값을 사용한다. `ui_viz`의 Button D-087 색을 HM Runtime Semantic stop에 연결한 것은 현재 저장소의 **구현된 Runtime 계약**이다. 운영 allowlist로 승격하기 전에 디자인팀이 역할 이름·범위·호환성을 승인해야 한다.
초기 검증 정책은 색상은 승인된 형식(예: 불투명 hex), radius는 허용 범위의 `px` 값으로 제한한다. shadow, font, spacing을 추가할 때에는 **각각 별도 타입·범위·호환성 검사**를 설계한다. 임의 CSS 함수, `url()`, `var()`, `!important`, selector, JavaScript는 Theme 값으로 허용하지 않는다. 접근성 검토는 개별 값의 형식 검사와 별도로 수행한다.
## 5. 앱 Theme Loader의 책임과 실패 처리
1. 앱이 기본 Theme CSS를 먼저 로드한다.
2. API/CDN에서 활성 버전 포인터와 불변 Theme 문서를 가져온다. timeout과 캐시 정책을 명시한다.
3. 문서 전체의 스키마 버전, ID, allowlist, 값 타입·범위를 검증한다. **하나라도 실패하면 일부 키만 적용하지 않는다.**
4. 검증이 끝난 뒤 이전 Runtime override 키를 제거하고 허용된 키만 적용한다. Theme을 교체할 때 남은 오래된 키가 기본값을 가리지 않게 한다.
5. fetch·검증 실패 시 Runtime override를 제거하고 기본 CSS를 사용한다. 실패 원인과 적용한 버전을 기록한다.
6. 운영 초기에는 새 페이지 진입·새로고침 때 최신 활성 버전을 읽는다. 열린 화면의 실시간 교체는 별도 요구가 생길 때 도입한다.
현재 적용 함수는 unknown 입력을 자체 검증하며 문서 전체 실패 시 atomic fallback한다. Global은 document root, Scoped는 명시적 wrapper에 적용하고 cleanup으로 host 기존 inline 값/priority를 복원한다. Theme ID 확인·URL/캐시 정책은 서비스 책임이며 공개 URL Loader는 HTTP/JSON/Runtime 계약을 검증한다. 렌더 중 깜빡임이 문제라면 초기 Theme 로딩 순서와 CSS 적용 시점을 앱 bootstrap에서 조정한다. 캐시는 성능용이며 검증과 기본값 fallback을 대체하지 않는다.
**서비스 전역 Theme는 Global Runtime Theme를 권장한다.** `applyRuntimeTheme(document.documentElement, theme)` 또는 `loadRuntimeTheme(document.documentElement, themeUrl)`로 적용하면 body Portal도 같은 Theme를 받는다.
**Scoped wrapper Theme는 wrapper 밖 body Portal에 자동 전달되지 않는다.** SelectContent, PopoverContent(Date Picker·Combobox 포함), DropdownMenuContent/ContextMenuContent 등 내부 body Portal popup은 Global Theme를 따른다. 이 Content wrapper에는 Theme scope를 전달하는 `container` API가 없다. 별도 export된 Radix Portal wrapper의 props를 Content의 container 지원으로 간주하지 않는다. Scoped는 DOM 자손에만 적용되며 세부 apply/merge/fallback/scope 계약은 [Runtime Theme](runtime-theme.md)를 따른다.
## 6. Storybook 작성·검수 기준
- 현재 Story import는 `@hm-design/ui`의 공개 진입점과 기본 CSS를 사용한다(`@company/ui`는 장기 운영 목표명). 로컬 내부 파일을 직접 가리켜 패키지 배포 결과와 달라지는 구성을 피한다.
- 컴포넌트별 공개 props, variant/state, 오류·비활성·로딩, 키보드/포커스 동작을 지원 범위에 맞게 기록한다.
- 현재 Theme verification Story에서 Default/External과 Runtime 계약을 비교한다. Theme selector toolbar는 없다. Preview는 운영 Publish를 수행하지 않고 패키지 Runtime API를 사용한다.
- 값 검수는 실제 컴포넌트의 기본/hover/focus/disabled/error 상태, 대비·가독성, 잘림·넘침을 포함한다. Theme 변경이 닿는 Story에 집중한다.
- 검수 기록에는 `@company/ui` 버전, Theme 버전 또는 Draft ID, 결과를 남긴다. Storybook 검수 후 실제 앱에서도 같은 계약이 적용되는지 통합 확인한다.
## 7. 개발 완료 기준
- 단일 저장소에서 만든 `@company/ui`의 공개 컴포넌트·기본 CSS를 서비스 앱이 버전으로 설치하고, Theme 장애에도 기본값으로 동작한다.
- 별도 제공되는 승인된 Semantic Token 값만 변경해 Button 등 대표 컴포넌트가 앱 재빌드 없이 새 진입·새로고침에서 바뀐다. 패키지 CSS 규칙 변경에는 새 패키지 버전이 필요하다.
- 패키지 Runtime API는 잘못된 키·값·스키마 버전을 거부한다. 서비스별 themeId 일치 검사는 향후 Loader가 담당한다(현재 API가 ID를 검증한다고 간주하지 않는다).
- Theme 교체·해제 시 이전 override가 남지 않는다.
- Storybook Preview와 앱에서 동일한 Theme 문서를 같은 계약으로 검증한다.
- 컴포넌트 API 변경은 Theme Publish만으로 처리하지 않고 패키지 릴리스와 앱 업데이트로 전달한다.
운영 절차는 [운영/배포 가이드](운영-배포-가이드.md)를 따른다.
## 8. Publisher / Developer 화면 조합 확인
사용 요소는 [Component / Pattern 인덱스](component-pattern-reference.md)에서 선택하고 기존 Component → Variant / Props → Pattern / Composition 순으로 해결한다. 실무 예시의 숫자를 새 공통 규칙으로 만들지 않는다. 이번 사전공유는 Tabs 조합과 디자인 방향에 관한 자료이며 Dashboard·GNB·LNB·Filter·Loading의 새 규격을 제공하지 않는다.
### 8.1 탭 조합과 역할별 간격
| 확인 항목 | 현재 기준 / 실무 주의 |
|---|---|
| 탭 콘텐츠의 중첩 박스 | [Tabs §16](../components/tabs.md#16-publisher--developer-조합-안내-2026-10-09). Card 안은 surface, content 페이지 위는 content. TabsContent의 중복 배경·border·좌우 padding 제거 |
| 역할별 간격 | [PageHeader](../patterns/page-header.md): 제목→설명 8px, 아래 콘텐츠 20px. [기본 Card](../components/card.md): 여백·Header→Content 24px. TabsContent `py-6`는 조합 예시로만 사용 |
| Typography / Color | [Typography Reference](../../src/foundation/typography.stories.tsx), [Color Reference](../../src/foundation/color.stories.tsx)와 각 Component 문서. CardTitle·PageHeaderTitle·Tab label을 같은 크기로 통일하지 않음 |
| Spacing / Layout | [Layout Reference](../../src/foundation/layout.stories.tsx). 전역 spacing scale은 미정의. Card 전용 Token·비교 이미지의 48px을 화면 전체 gap으로 재사용하지 않음 |
| Theme 연결 | 기존 앱 CSS entry의 Tailwind / 공개 HM CSS를 재사용. [소비자 검색 범위](runtime-portability.md#21-tailwind-v4-소비자-검색-범위)를 확인하고 새 utility가 실제 생성되는지 검증 |
| State | Selected·Inactive·Hover·Focus·Disabled는 기존 Tabs 계약. Empty / Loading / Error는 기존 [Component / Pattern](component-pattern-reference.md)을 조합하고 소비자가 데이터 상태를 관리 |
| Responsive | 실제 부모의 수축(`min-w-0`)과 문서 넘침 확인. Tabs는 기존 넘침 버튼, [Table](../components/table.md)은 기본 최소 폭 520px·내부 스크롤 유지. 페이지 Navigation은 기존 단일 모드 유지 |
| Accessibility | tablist 접근 가능한 이름, Trigger / Content value 일치, 연결된 입력 Label, 방향키·Home/End·focus-visible·disabled 건너뛰기 확인. 입력 초안 유지가 필요하면 소비자 상위 state 사용 |
### 8.2 디자인 방향과 실제 반영 상태 구분
- 사전공유 v0.2의 중첩 박스 제거(D-01)는 기존 API만으로 가능하다. TabsContent는 원래 배경·border를 자동 추가하지 않으므로 소비 코드를 조정한다. 고유 Input·Table·Alert 스타일은 제거하지 않는다.
- 상단 White 80%(D-02)는 사용자의 2026-10-09 실제 구현 요청에 따라 Tabs의 Primary content branch에 반영했다. `background-image` 상단 stop의 alpha만 0.8이며 전체 탭 opacity는 1, Secondary·Legacy·Disabled 상태 규칙은 유지한다. 공통 Palette·Theme JSON을 80%로 바꾸지 않는다. [Theme 사용과 Source 수정](테마-토큰-가이드.md#theme-사용과-theme-source-수정) 및 [Tabs 실제 반영 범위](../components/tabs.md#163-d-02-상단-흰색-80-실제-반영)를 따른다.
- 대표 Story와 Fill verification 예제 모두 중첩 박스 없는 패널을 사용한다. Legacy Compatibility는 이전 조합 보존 목적이며 신규 운영 디자인 기준으로 사용하지 않는다. AI 화면은 [AGENTS.md](../../AGENTS.md)에 따라 실제 HM React Component를 렌더하며 별도 HTML 모방 화면을 제출하지 않는다.
- 검증은 최종 통합 단계에서 필요한 빌드·정적 Guard·실제 브라우저 확인을 수행한다. 배경 transparent·border 0·padding 24px 0, 자식 정렬과 고유 스타일, 실제 gradient alpha·전체 opacity, desktop / mobile 넘침과 핵심 동작을 구분해 기록한다. 정적 PASS로 브라우저 확인을 대체하지 않으며 미실행은 `NOT VERIFIED`로 표시한다.
이번 비교 분류·변경 파일·검증 범위는 [사전공유 통합 보고](publisher-integration-2026-10-09.md)를 따른다.