# SW 패키지 통합 개선 구현·검증 기록 (2026-10-19) - 대상: `@hm-design/sw` 0.1.0 → **0.2.0**, 브랜치 `feature/sw-package`, 기준 Commit `5faa1f1`(작업 시작 시 로컬 = 원격). - 범위: 승인된 통합 계획 1~8단계(빌드 재현성, 숨김 API, 도구 버튼, 로고 옆 파일 열기·실측 반응형, `SWPopup` 자유 콘텐츠·Escape 스택, Popup 안 Dropdown, 조합형 반응형, 문서·Storybook·소비 검증). - Commit·Push·Publish 없음. 증거: [integrated-improvement-qa/](./integrated-improvement-qa/). ## 1. 단계별 결과 | 단계 | 내용 | 판정 | |---|---|---| | 1 빌드 재현성 | 원인: 체크아웃 줄바꿈. 작업 폴더의 `ai/`·README 등 16개 파일이 CRLF였고(`core.autocrlf=true`), CSS에 base64로 들어가는 SVG도 CRLF 체크아웃이면 바이트가 달라짐. `//#region` 경로는 경로가 달라도 동일(원인 아님). 조치: `build.mjs`가 dist 텍스트를 LF로 정규화하고 `dist/build-info.json`(commit, dirty, vite·rolldown·typescript 버전, 시각 없음) 기록, `packages/sw/.gitattributes`(`eol=lf`), 작업 폴더 16개 파일 LF 변환(내용 diff 없음), 0.2.0 | PASS | | 2 숨김 API | `hiddenControls`에 `documentNew`·`documentOpen`·`layerFreeze`·`layerLock`(`SWHeaderSubControlId`). 하위 Props `showNew`/`showOpen`, `showFreeze`/`showLock`, 속성창 `menus.showLayerFreeze`/`showLayerLock`. 동결·잠금은 20px 자리 유지 + 그리지 않음(가짜 버튼 없음). 하위 항목 숨김은 레이아웃 모드·Breakpoint를 바꾸지 않음 | PASS | | 3 도구 버튼 | `SWToolButton`(`text` = 속성Bar/속성창 토글, `pill` = 인쇄·K, `document` = 파일명 면), `SWToolGroup`(`text` 상세보기 묶음 / `pill`, `selection="single"` radiogroup). 새 디자인 값 없음 | PASS | | 4 파일 열기 | `documentOpenPlacement="header"`: Home 바로 뒤에 `hm-sw-header__document-button` 면으로 Open, Dropdown Open 숨김, `onDocumentOpen`·`documentOpenLabel` 재사용. `leading` 슬롯. 측정 폭을 `--hm-sw-header-leading-width`로 파일명 clamp에서 빼고, Open이 보이는 동안 다른 필드는 줄지 않게 해 실제 넘침이 생기면 Open을 More로 이동(되돌릴 때 Open 폭 히스테리시스). D-1 12px 여백 | PASS | | 5 SWPopup | 속성창 프레임을 `SWPopupFrame`(내부)으로 분리해 `SWPopup`과 `SWHeaderPropertyPanel`이 같은 DOM 사용. `children`·`title`·`autoFocus`, `selection`/`properties`/`groups` 선택형. Escape 스택(안 = 그 Popup, 밖 = 최근 사용 Popup 하나, `defaultPrevented` 존중), autoFocus 시 안에서 닫히면 Focus 복귀 | PASS | | 6 Popup 안 Dropdown | 실측: 짧은 Popup에서 레이어 목록이 본문 경계에서 잘림 확인 → 속성창 Picker의 고정 위치 계산을 `useSWFixedDropdownPosition`으로 추출해 Picker와 공개 Dropdown 5종이 공유(중복 구현 없음). `SWPopupSection` / `SWPopupRow` 추가 | PASS | | 7 조합형 반응형 | 새 More 로직 없음. `SWHeader`의 `controls`·`documentOpenPlacement`·`leading`·`tools`로 조합형 구성이 기존 More·실측 판단을 그대로 사용. `SWTheme` 직접 배치에는 More 없음(문서화) | PARTIAL(도구 막대 자체는 More로 이동하지 않음, §6) | | 8 문서·Storybook·소비 검증 | AI 가이드 4종·예제 E/F·README·사용 가이드, Story 3개 파일(5 Story: `SW/Header`의 Document Open·Hidden Sub Controls, `SW/Tool` 1, `SW/Popup` 2. 별도 Options 메뉴 없음), 독립 소비 프로젝트(SW + HM UI) | PASS | ## 2. 추가·변경 공개 API (모두 선택, 기본 동작 동일) | 대상 | 추가 | |---|---| | `SWHeader` | `documentOpenPlacement?: "menu" \| "header"`, `leading?: ReactNode`, `hiddenControls`의 `SWHeaderSubControlId` 4개 | | `SWHeaderDocumentTitle` | `showNew`, `showOpen`(둘 다 false → 하단 없음, 문서도 없으면 Dropdown 없음) | | `SWHeaderLayerMenu` · `SWHeaderLayerControl` | `showFreeze`, `showLock` | | `SWHeaderPropertyPanel` | `children`, `title`, `autoFocus`, `selection`·`properties`·`groups` 선택형, `menus.showLayerFreeze`/`showLayerLock` | | 신규 | `SWPopup`, `SWPopupSection`, `SWPopupRow`, `SWToolButton`, `SWToolGroup` 및 `*Props`, `SWToolButtonAppearance`, `SWHeaderSubControlId` | | 내부(비공개) | `SWPopupFrame`, `useSWFixedDropdownPosition`, `SWHeaderMore.forceVisible` | 호환성: 기존 Props·Export 제거·이름 변경 없음. 동작 변경은 Escape 하나(여러 Popup이 동시에 열린 경우 바깥 Escape가 모두가 아니라 최근 사용 Popup 하나만 닫음, Popup 안에서 이미 처리된 Escape 무시). Header 단일 속성창의 Escape 닫기·Focus 복귀는 기존 overview 회귀로 확인. ## 3. 기존 디자인 유지 - 기존 SW Story 10개 × 1920 + Overview·Responsive More × 9개 폭 = 28장, 변경 전 기준선과 **픽셀 단위 28/28 동일**(threshold 0, 기준선 자체 2회 캡처 28/28 동일). [compare-result](./integrated-improvement-qa/visual-diff/compare-result.json) - 소비 프로젝트에서 SW·HM UI CSS 순서를 바꿔도(SW 먼저 / UI 먼저) 대표 SW·UI 요소 15종 computed style 동일. Tailwind 4 preflight는 `@layer base`, SW CSS는 layer 밖이라 충돌 없음. ## 4. 반응형 (Chrome, 실측) | 폭 | 1920 | 1800 | 1440 | 1180 | 1080 | 900 | 768 | 640 | 520 | 390 | |---|---|---|---|---|---|---|---|---|---|---| | 기본 Header + Open: Open | 표시 | 표시 | 표시 | 표시 | 표시 | 표시 | 표시 | 표시 | More | More | | 파일명 폭(px) | 250 | 190 | 310 | 120 | 120 | 120 | 179 | 96 | 103 | 96 | | 레이어 필드(px) | 234 | 234 | 234 | 234 | 234 | 234 | 234 | 234 | 234 | 113 | | 조합형(controls) Open | 표시 | 표시 | 표시 | 표시 | 표시 | 표시 | More | More | More | More | - 모든 폭: Header 49px, 막대·객체 영역 넘침 ≤ 1px, 부품 겹침 없음, 오른쪽 끝 밖으로 나가는 부품 없음, More의 Open 실행 확인. - 히스테리시스: 520 → 1080 확대 시 600px에서 한 번만 복귀(깜빡임 없음). D-2는 고정 640px 대신 실측으로 결정(기본 520 미만·조합형 768 이하에서 이동). - 사선 접점: Open은 기존 파일명과 같은 위치(로고 오른쪽 − 15px, 48px)에서 시작, 파일명은 Open 뒤에서 12px 여백. 조합형(`SWHeaderHome` → `SWToolButton document` → `SWHeaderDocumentTitle`)도 같은 값. ## 5. Popup 자유 콘텐츠·Dropdown - `SWPopup` 3종 프레임 = 속성창 프레임(314, `#E4DBC9`, `#A67D03`, 4px, 제목 `#FFC600`, 제목줄 30, dialog / aria-modal=false) 동일. - children: SW `SWHeaderInput` 행(확정·잘못된 값 복원), compact 41px 필드, 읽기 전용 행, SW 레이어·색상 Dropdown, HM UI Switch·Select(Portal)·Slider·Button 모두 동작. 기존 `SWHeaderPropertyPanel`에 children 추가 시 그룹 뒤 같은 본문에 배치. - Dropdown: Popup 안에서 `position: fixed`로 필드 바로 아래 열림, Popup 밖으로 나가도 끝까지 보임(elementFromPoint), Escape는 Dropdown만 닫음. - 여러 Popup: 편집 중 Input Escape = 편집 취소만, Popup 안 Escape = 그 Popup만, 바깥 Escape = 최근 사용(Focus·클릭) Popup 하나. 긴 콘텐츠는 본문 스크롤로 마지막 행 도달. ## 6. 남은 문제·미검증 - **[미해결] 좁은 화면에서 문서명·레이어 필드가 사라짐:** `tools`·`leading`의 소비 콘텐츠(`SWToolGroup` 포함)는 More로 이동하지 않고 폭을 유지합니다. 그래서 넓은 도구 막대가 있으면 `controls` 필드가 대신 줄어듭니다. 예제 F(도구 237px) 실측: 1080px에서 Open은 More로 이동, 640px 문서명 64 · 레이어 93px, 520px 33 · 48px, **390px 문서명·레이어 필드 폭 0(사실상 사라짐)**. 막대 넘침은 없지만 필드를 쓸 수 없습니다. 도구 막대 항목을 More로 옮기는 기능이 필요하며 이번 범위에서는 해결하지 않았습니다(해결 계획: 커밋 보고 §남은 문제 1). - **Popup 안 Header Dropdown 외형:** 동작은 정상이지만 어두운 Header 필드가 밝은 Popup 면 위에 그대로 그려집니다(디자인 미정). 행 모양 선택이 필요하면 속성창 `picker`. - **디자인 미확정 보류:** `SWToolStatus`, Popup 하단 버튼(적용·취소) 전용 스타일. - `index.ts`의 `SWHeaderSubControlId` export 한 줄은 다른 세션(Figma 병렬 작업)이 같은 폴더에 같은 내용으로 추가한 것으로 보이며, 이 계획과 같아 유지했습니다. - NOT VERIFIED: Firefox·Safari, 실제 모바일 기기, 스크린 리더 음성, 실제 Registry publish, Popup `anchorRef` 경로의 새 Story(기존 Header 속성창 경로는 overview 회귀로 확인). ## 7. 실행한 검증 | 검증 | 결과 | |---|---| | `tsc -b`(루트) / `npm run typecheck -w @hm-design/sw` | exit 0 / exit 0 | | [integrated-improvement.browser.mjs](../../packages/sw/tests/integrated-improvement.browser.mjs) (옵션·조합형·도구·Popup) | 97/97 PASS, 콘솔 오류 0 | | [integrated-consumer.mjs](../../packages/sw/tests/integrated-consumer.mjs) + browser (React 19.3 · TS 7.0 · Vite 8.3 · Tailwind 4.3, SW + UI Tarball, CSS 2순서) | tsc·build 성공, 42/42 PASS | | 기존 A~D 소비([ai-template-consumer](../../packages/sw/tests/ai-template-consumer.mjs)) | 27/27 PASS | | [ai-docs.check.mjs](../../packages/sw/tests/ai-docs.check.mjs) | 21/21 PASS(예제 E·F 포함) | | [integrated-visual.browser.mjs](../../packages/sw/tests/integrated-visual.browser.mjs) | 28/28 픽셀 동일 | | [build-reproducibility.mjs](../../packages/sw/tests/build-reproducibility.mjs) | PASS: LF 2경로 × 2회 dist·Tarball 동일(`e98cc5ed…`), CRLF 복사본 dist 동일, 비-LF 파일 0 | | storybook-unused-removal / overview(기존 회귀) | 처음 실행 78/81 · 51/54: 6건 중 4건은 이 작업이 의도적으로 바꾼 범위 단언(Story 수 증가, SW 소스·AI 문서), 2건은 변경 전 기준선에서도 실패하던 overview 과거 단언. 4건은 검사 의미를 유지해 범위를 갱신(SW/Header 10개·다른 항목 동일, 추가는 승인된 5개 Story만, 다른 패키지·설정·자산 무변경, SW 변경은 통합 개선 파일 목록 안에서만) → **81/81 · 52/54**. Options 메뉴를 `SW/Header`로 통합한 뒤(Story 2개 렌더 검사 추가) **83/83 · 52/54** | | overview 남은 실패 2건 — **기존 실패, 신규 결함 아님** | "24 retained Story IDs…", "retained non-Overview declarations byte-identical…": 이미 은퇴한 Dropdown Story 시점(24개, 기준 Commit `50bf5bd`)을 고정 비교하는 과거 단언입니다. 이 작업을 시작하기 전 기준선(HEAD `5faa1f1`)에서도 같은 2건이 실패했습니다([baseline-overview/chrome-result.json](./integrated-improvement-qa/baseline-overview/chrome-result.json) `checks`의 FAIL 2건, 실행 로그 `baseline-overview.log`는 Git 제외(`*.log`) 로컬 기록). 현재 인벤토리는 storybook-unused-removal이 검증하며, overview 기능 검사 52건은 모두 PASS | | font-weight 정책 테스트 | 3/3 PASS |