Files
HMDesign/docs/guidelines/runtime-theme-report.md
T

16 KiB
Raw Blame History

External Runtime Theme 구조 점검·전환 결과

HISTORICAL_RECORD: 작성·검증 시점(2026-10-06)의 기록이다. 아래 리터럴·실측·당시 예외를 보존한다. 현재 apply/merge/fallback/scope와 Slider Focus는 Runtime Theme, key/property/type은 Runtime 계약표, CDN 배포는 Theme delivery를 따른다.

2026-10-06 · HM_THEME / Theme infrastructure

1. 현재 Theme Architecture

Primitive(packages/ui/src/theme/primitives.css) → HM Default preset / Semantic / shadcn adapter(default.css) → Component Mapping(theme/<name>.css) → CSS selector / Tailwind → UI. theme.css가 Default부터 import한다. Tailwind namespace 매핑은 tailwind.css, Demo artwork는 src/hm-theme.css, Storybook 설정은 .storybook/preview.tsx다.

대표 체인을 실제 파일에서 확인했다. 모든 Component Token의 기존 이름을 유지했다.

Component 실제 대표 체인
Checkbox green-light-2 → sem-state-active → checkbox-checked-background → checked background
Radio green-light-2 → sem-state-active → radio-checked-border-color / radio-dot-color
Switch green-light-2 → sem-state-active → switch-on-background
Slider green-light-2 → sem-state-active → slider-range-background / thumb-border-color
Progress green-light-2 → sem-state-active → progress-indicator-background
Calendar green-light-2 → sem-state-active → calendar-selected-background / range-edge-background
Pagination green-default → sem-action-primary → pagination-active-background
Badge green-default → sem-action-primary → badge-default-background
Table sem-accent → sem-surface-highlight(45%) → table-row-hover/selected-background
Input sem-stroke-input-focus → input-focus-border-color; sem-action-destructive → input-error-border-color
Select sem-accent → sem-surface-highlight → select-option-state-background; sem-stroke-input-focus → focus border
Button sem-action-primary → button-outline-green-ink; primary gradient stops → button-brand-green-background
GNB primary surface stop group → gnb-background; sem-action-primary → gnb-current-text
LNB Primary + palette shades → lnb-background; sem-action-primary → lnb-current-text

2. Runtime Theme 기존 방식

기존 src/theme/preview.ts는 schemaVersion 1 / 25개 allowlist / 6자리 HEX / radius 2–12px / gradient 전체 세트 검증을 제공했다. 전체 문서를 검증한 뒤 HTMLElement inline 변수에 적용하고 이전 주입 키를 제거했다. 실제 호출처는 없었고 서비스 패키지에서 import할 수 없었다. 새 엔진 없이 이 방식을 UI 패키지로 이동·보강했다.

3. 발견된 문제와 처리

  • Active, on Active, Accent, Subtle Surface, Inverse Text 계약 누락 → 공개 계약에 추가.
  • 일부 Button / Input / 패널 / navigation / palette 참조가 공개 Semantic을 우회 → 동일 Default 값을 유지하면서 Semantic/palette로 연결.
  • Button·Select의 직접 HEX와 shadow 고정 RGB → Default preset palette / token 기반 derived mix로 이동.
  • root에서 계산된 Component 별칭을 wrapper가 상속해 Scoped override가 전달되지 않음 → 명시적 data-hm-theme scope에서 별칭·Mapping 재계산.
  • cleanup이 host 기존 inline 값과 우선순위를 잃을 수 있음 → 값/priority/기존 scope를 복원.
  • Tailwind upstream root/dark preset과 HM preset 중복 → HM 색상 preset은 Default에 단일 소유, chart/sidebar fallback은 base layer 유지.
  • Accent 45% 반복 → 동일 Soft Highlight 정책 사용처를 내부 Semantic으로 승격.

4. 확정 Runtime Theme Contract

전체 표: runtime-theme-contract.md.

공개 입력 총 73개: 기존 25개, 핵심 역할 추가 6개, 선택적 Theme palette 42개. 모두 partial 입력 가능하며 Component Token 수백 개를 요구하지 않는다. CSS property 중복 매핑은 없다. 색은 불투명 6자리 HEX, radius는 기존 2–12px 정수다. Typography/spacing/layout/shadow geometry는 공개하지 않았다.

Runtime Token Default 의미 외부 override 주요 영향
color.action.primary #123328 Strong/Primary Yes Badge, Pagination current, Button outline, navigation ink
color.state.active #266651 Checked/Selected/ON Yes Checkbox, Radio, Switch, Slider, Progress, Calendar
color.stroke.input.focus #266651 일반 Focus Yes Input, Select, Checkbox, Calendar ring 등
color.accent #bddfd4 Highlight 원색 Yes Select/Menu/Calendar/Table/Pagination soft state
color.bg.content #f5f0e5 콘텐츠 면 Yes Dashboard, Card content, navigation current face
color.bg.surface #ffffff 기본 면 Yes Input, Select, Card, Floating/Modal
color.bg.subtle #f4f1ea 은은한 면 Yes Badge secondary, Table footer, Calendar today
color.fg.primary #0a221d 주요 텍스트 Yes 일반 UI 텍스트
color.fg.inverse #ffffff 반전 텍스트 Yes Button, Badge, GNB/LNB, Tooltip
color.stroke.default #9a9284 기본 border Yes Checkbox, Radio, Badge outline, Scroll Area
color.action.destructive #d32f2f 오류/위험 Yes Input error, destructive Badge/Button/Menu

5. Default Theme 구조

Default CSS preset은 최초 렌더의 fallback이다. defaultRuntimeTheme는 같은 contract의 기본 Theme Set이다. 이를 주입한 실제 화면과 주입하지 않은 화면의 parity를 검사했다. 외부 JSON 값은 동일 CSS property를 override한다. Primitive 유지, Semantic 유지, Component Mapping 유지. Component는 외부 객체를 읽지 않는다.

Default → External 우선순위는 scope inline CSS로 보장한다. 기존 Component/Pattern import 순서는 유지하며 Filter Bar padding 순서도 바꾸지 않았다.

6. Active / Focus 독립성

Verification Theme는 Active #007f73, Focus #c75b00이다. Checkbox checked와 Calendar selected의 실제 배경은 rgb(0,127,115), 실제 focus-visible shadow는 orange였다. 두 역할을 각각만 변경해 다른 core 역할의 Default 값이 유지되는지 검사했다. Default에서는 두 값 모두 Light 2여도 서로 alias하지 않는다.

7. Primary / Active 독립성

Primary #6837a8, Active #007f73. Badge default / Pagination current / Button outline / GNB·LNB current ink는 Primary, 선택 Control과 Calendar edge는 Active다. Checkbox/Radio unchecked hover border와 Slider halo, Accent foreground는 기존 Primary 정책을 유지하므로 Primary 변경의 실제 영향표에 포함했다.

8. Accent 및 영향표

결정: PROMOTE_TO_SEMANTIC. --hm-sem-surface-highlight = color-mix(in srgb, var(--hm-sem-accent) 45%, transparent)는 내부 derived다. 외부 사용자는 Accent만 지정한다. Highlight 비율은 바꾸지 않았다. Table header와 Tab의 과거 팔레트 장식, Toggle on의 full Accent는 각각 기존 정책을 유지한다.

Component Primary Active Focus Accent
Checkbox unchecked hover border checked ring —
Radio unchecked hover border selected border/dot ring —
Switch — ON ring —
Slider hover/focus halo(기존) range/thumb border Primary halo(기존) —
Progress — fill — —
Calendar highlight text selected/edge ring hover/middle
Pagination current/highlight text — ring hover
Badge default — — —
Table — — — row hover/selected
Input — — border/ring —
Select highlight text — border/ring selected/highlight
Button outline/ghost/link — ring —
GNB current/open ink — mobile ring; desktop inverse outline mobile highlight
LNB current/gradient first stop — inverse outline(기존) —

Accent를 단독 변경해 Primary/Active/Focus/Destructive가 변하지 않음을 검사했다. Global Select Portal의 선택 옵션은 yellow Accent 45%(color(srgb 1 0.827451 0.427451 / 0.45))로 실제 렌더됐다. DatePicker Portal의 selected day도 Global Active를 받았다.

9. Hardcode Audit

전체 파일·줄·값·분류·조치는 CSV에 있다. 변경 전 UI Theme CSS의 하드코딩 후보 25개 발생 지점을 처리했고 변경 후 Component CSS의 후보는 0이다. 주석은 실행 리터럴에서 제외했다. Primitive, Default preset, 문서 예제, Story, Demo, 독립 CENTER를 구분했다.

파일 값 분류 조치
theme/primitives.css #266651 / #123328 등 VALID 기존 Primitive 유지
theme/button.css #221d13 / #a98d53 / orange / gray 등 HARDCODE_CANDIDATE 공개 Theme palette 기본값으로 이동
theme/select.css #2d2d2d HARDCODE_CANDIDATE palette-indicator 연결
theme/dialog.css / sheet.css 등 rgb(0 0 0 / 0.2) HARDCODE_CANDIDATE palette-black 기반 mix, shadow geometry 유지
theme/dashboard-layout.css rgb(7 30 24 / 0.18) HARDCODE_CANDIDATE palette-green-dark-2 기반 mix
theme/lnb.css rgb black 20%/30% HARDCODE_CANDIDATE palette-black 기반 depth gradient
theme/checkbox.css 등 #0000 / transparent INTENTIONAL ring sentinel / 투명면 유지
Select/Menu/Table 등 Accent 45% color-mix DERIVED 같은 의미의 내부 surface-highlight로 승격
components/ui/* overlay bg-black/10 INTENTIONAL upstream class 유지; 실제 색은 overlay.css의 derived Semantic
src/*stories.tsx / .storybook specimen HEX STORY_ONLY 검수 specimen 유지
src/hm-theme.css artwork HEX INTENTIONAL Demo 고정 artwork 유지
packages/center-graphic 그래픽 palette INTENTIONAL 독립 CENTER 도메인 유지

10. Component Mapping 변경

기존 Mapping 이름/size/props를 삭제하거나 rename하지 않았다. Primary/Destructive/Surface/Text/Border 참조를 공개 Semantic으로 연결하고, 별도 브랜드 shade는 선택적 Theme palette로 연결했다. 모든 preset Mapping 선언에 명시적 Theme scope를 추가했다. Accent mix와 shadow chroma를 derived로 정리했다. UI 컴포넌트·Pattern .tsx는 수정하지 않았다.

11. Storybook Verification Theme

src/theme/runtime-theme.stories.tsx의 숨김 verification Story: Comparison, GlobalExternal, Verification, Dashboard. 요청한 15종과 Command를 함께 렌더링한다. Production 기본값에 검증색을 넣지 않았다.

Verification은 브라우저에서 136개 검사, 63개 렌더된 data-slot 부품을 확인한다. 1280 / 768 / 390px에서 모두 PASS. Default는 변경 전 토큰 그래프와 색상/geometry parity, full Default preset 주입 parity를 검사한다. 공개 core/palette는 개별 주입, gradient는 complete-group 주입을 검사한다. 포커스 ring은 실제 focus-visible 및 색 공간을 고려해 확인한다. Theme switching 중간 transition 프레임을 제외하기 위해 이 검사 화면만 transition-duration을 0으로 고정한다. Production transition은 유지한다.

추가로 _local/check-theme-geometry.mjs는 변경 전 snapshot과 최종 CSS 구문을 비교한다. 50개 파일, 1,633개 geometry 선언 변경 0. Default/External 비교만으로 검사 로직을 대체하지 않았다.

12. Partial Override / Scope

Partial은 미지정 Default 유지. 빈 tokens는 Default. invalid 문서는 이전 override를 원자적으로 해제해 host/Default 복귀. 교체 때 stale key 제거. 기존 host inline 값·priority·scope 복원.

Global A 아래 Scoped B는 독립 HM preset에서 시작한다. B가 지정하지 않은 값은 HM Default이며 A를 상속하는 합성 정책은 아니다. Portal은 DOM inheritance를 따른다. 현재 HM SelectContent/PopoverContent/DatePicker는 container API를 노출하지 않으므로 wrapper Theme만으로 body popup을 scoped하게 만들 수 없다. Global popup 전달은 검증했으며 이 제한을 숨기거나 새 Portal API로 우회하지 않았다.

13. 수정 파일

  • packages/ui/package.json: ./theme 진입점 추가.
  • packages/ui/src/theme/{contract,runtime,index}.ts: 단일 계약, 기존 검증/주입 확장, Default preset.
  • packages/ui/src/theme/default.css, theme.css, tailwind.css: preset/derived/scope/import ownership.
  • packages/ui/src/theme/*.css: 기존 50개 Component/Pattern Mapping의 scope / 색상 의존 관계. overlay.css 추가.
  • src/theme/preview.ts: 기존 이름 호환 어댑터.
  • src/theme/runtime-theme.stories.tsx, verification-baseline.json: Story 전용 검증.
  • docs/guidelines/{runtime-theme,runtime-theme-contract,runtime-theme-report}.md, audit CSV, 테마 토큰 가이드, HM inventory, CLAUDE.md: 계약/결과 문서.

변경 전 CSS/Preview는 _local/theme-before/에 보존했다. Git 저장소가 없으므로 git diff 대신 snapshot 비교를 사용했다. Dependency 추가 없음.

14. API 변화

Component API 변화 없음. Theme infrastructure에 필요한 최소 공개 경로 @hm-design/ui/theme와 applyRuntimeTheme, resetRuntimeTheme, runtimeThemeContract, defaultRuntimeTheme, 타입 2개를 제공한다. 기존 applyPreviewTheme/resetPreviewTheme 이름은 같은 구현의 어댑터다. 네트워크 fetch·캐시·auth·hydration·Publish는 추가하지 않았다.

15. 문서

Runtime 사용·정책, 전체 계약표, 감사 CSV를 추가하고 기존 테마 가이드/CLAUDE/HM inventory에 구현 상태를 연결했다. Default/External, 계층, partial/invalid, 적용 예제, scope/Portal, 초기 렌더 책임을 실제 API 기준으로 설명했다.

16. Build / 통합 검증

  • npm run typecheck: PASS.
  • npm run build: PASS.
  • npm run build-storybook: PASS.
  • Storybook browser: 1280/768/390px 각 136 checks PASS.
  • CSS geometry snapshot 비교: PASS (1,633 declarations).
  • 기존 Dashboard: 1280px GNB 70px / LNB 272px / main padding 34px, 390px GNB 56px / LNB display none / main padding 20px. 문서 가로 overflow 없음.
  • LNB Standalone Mobile: 390px 서비스 메뉴 Sheet 열기/닫기, 중첩 메뉴 표시, Sheet width 342px, 문서 가로 overflow 없음.
  • Global Select/DatePicker Portal: 실제 Accent/Active 전달 확인.
  • 기존 CSS 예제 bg-[var(...)] warning, Storybook 500kB chunk warning 유지. Sandbox에서 Vite/esbuild EPERM이 발생해 동일 명령을 허용된 정상 실행 권한으로 재실행했다.

17. FOLLOW_UP

  • 서비스 네트워크 Theme Loader/API/CDN/Publish는 별도 단계다. Bootstrap 전에 동기 Theme 적용은 가능하지만 네트워크 이후 flash의 실제 운영 측정은 미수행.
  • Scoped popup이 필요하면 기존 HM Portal 경계의 API 설계를 별도 판단한다. 이번에 Component API/Portal을 바꾸지 않았다.
  • Slider halo 및 GNB/LNB inverse focus outline을 일반 Focus 정책으로 통합하려면 Default 디자인 변경을 포함해 별도 결정한다.
  • Light/Dark와 Theme selector UI, browser별 렌더 차이, 새 accessibility 대비 승인 절차는 이번 구조 작업 범위 밖이다.

검증 화면

Global External에서 실제 Select Portal을 연 과거 검증 화면은 _local/runtime-theme-global-select.jpg로 기록되었으나 현재 작업 폴더에는 파일이 없다. 해당 과거 이미지 근거는 NOT VERIFIED다. Primary/Active/Focus/Accent/Destructive의 서로 다른 값 적용 계약은 Runtime Theme 계약에서 확인한다.

작업 시간 (Asia/Seoul)

  • Started At: 2026-10-06 14:40:37 KST
  • Completed At: 2026-10-06 15:01:50 KST
  • Actual Duration: 21분 13초
  • Estimated Duration: 최초 45–60분
  • Variance: 예상보다 23분 47초–38분 47초 단축

후속: 디자인팀 실시간 Theme 운영 — 2026-10-06

이 문서 앞부분은 이전 Runtime 전환 기록이다. Git baseline d1015309ab43fba69c8959a3dda660358e0e8fa1 이후 JSON을 컬러 원본으로 연결하고 독립 Theme artifact/URL Loader/Storybook Preview를 추가한 결과는 Designer Theme Report에 기록했다. 사용 방법은 디자인팀 Theme 가이드, 운영/배포는 Theme delivery다. 과거 수치를 새 검증 수치로 대체하지 않았다.