Files
dwg-dxf-viewer-hm/docs/hmdesign-session-report.html
minsung 15ef6efdd8 docs: add HMDesign porting session report
HTML write-up of the React19+HMDesign porting session: timeline,
submodule+workspace integration, UI-to-component mapping, the
StrictMode double-mount bug and fix, and verified vs unverified
status.

Refs #2
2026-10-12 18:10:01 +09:00

259 lines
12 KiB
HTML

<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HMDesign 적용 작업 기록 — 2026-10-12</title>
<style>
:root {
--bg: #ffffff;
--fg: #1b1f24;
--muted: #6b7280;
--border: #e2e4e9;
--card: #f7f8fa;
--accent: #2563eb;
--ok: #15803d;
--ok-bg: #ecfdf3;
--warn: #92400e;
--warn-bg: #fef6e7;
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--sans: -apple-system, "Segoe UI", Roboto, "Noto Sans KR", sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1117;
--fg: #e6edf3;
--muted: #9198a1;
--border: #30363d;
--card: #161b22;
--accent: #60a5fa;
--ok: #4ade80;
--ok-bg: #0f2a1a;
--warn: #fbbf24;
--warn-bg: #2a2110;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
line-height: 1.6;
}
main {
max-width: 880px;
margin: 0 auto;
padding: 40px 20px 80px;
}
header h1 {
font-size: 1.6rem;
margin: 0 0 4px;
}
header p {
color: var(--muted);
margin: 0 0 32px;
font-size: 0.9rem;
}
h2 {
font-size: 1.15rem;
margin: 40px 0 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
h3 {
font-size: 1rem;
margin: 20px 0 8px;
}
p, li { font-size: 0.92rem; }
code {
font-family: var(--mono);
font-size: 0.85em;
background: var(--card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
}
pre {
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px 16px;
overflow-x: auto;
font-size: 0.82rem;
}
pre code { background: none; border: none; padding: 0; }
table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
margin: 12px 0 20px;
}
th, td {
text-align: left;
padding: 7px 10px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
th { color: var(--muted); font-weight: 600; }
.badge {
display: inline-block;
font-size: 0.72rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 999px;
}
.badge.ok { background: var(--ok-bg); color: var(--ok); }
.badge.warn { background: var(--warn-bg); color: var(--warn); }
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 18px;
margin: 12px 0;
}
.meta-list { list-style: none; padding: 0; margin: 0; }
.meta-list li { margin-bottom: 6px; }
.meta-list b { color: var(--muted); font-weight: 500; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
ul { padding-left: 1.3em; }
.timeline { border-left: 2px solid var(--border); padding-left: 18px; margin: 12px 0; }
.timeline .entry { margin-bottom: 14px; }
.timeline time { display: block; font-family: var(--mono); font-size: 0.78rem; color: var(--muted); }
</style>
</head>
<body>
<main>
<header>
<h1>HMDesign 적용 작업 기록</h1>
<p>dwg-dxf-viewer-hm · 2026-10-12 · Gitea 이슈 <a href="https://gitea.hmac.kr/kimminsung/dwg-dxf-viewer-hm/issues/2">#2 hmdesign 적용</a> / <a href="https://gitea.hmac.kr/kimminsung/dwg-dxf-viewer-hm/issues/1">#1 디자인팀 디자인 시스템 적용</a></p>
</header>
<h2>개요</h2>
<p>
기존 vanilla TS+Vite+Three.js DWG/DXF 뷰어(<code>dwg-dxf-viewer-sample</code>)를 건드리지 않고,
이 저장소(<code>dwg-dxf-viewer-hm</code>)에 <b>React 19 + HMDesign 디자인 시스템</b> 기반으로
UI/UX를 새로 구성했다. 뷰어 렌더링 엔진(<code>Viewer2D.js</code> + 파서들)은 로직을
그대로 포팅하고, 그 위의 모든 UI(툴바·레이어/속성/옵션 패널·좌표 표시·라이선스 팝업·로딩바)는
HMDesign 컴포넌트로 새로 디자인했다. 기존 UI의 색/아이콘/레이아웃은 가져오지 않았고,
<b>기능만</b> 1:1 유지하는 것을 목표로 했다.
</p>
<h2>작업 흐름</h2>
<div class="timeline">
<div class="entry">
<time>16:59</time>
<div><b>Gitea 퍼블리시</b> — <code>dwg-dxf-viewer-hm</code>을 public repo로 생성, 로컬 git init 후 최초 push.</div>
</div>
<div class="entry">
<time>17:01</time>
<div><b>이슈 #1 생성</b> — "디자인팀 디자인 시스템 적용": 앞으로 구현되는 UI/UX 작업을 커밋으로 연결.</div>
</div>
<div class="entry">
<time>17:4x</time>
<div><b>포팅 계획 확정</b> — 현재 디렉토리 재사용 / HMDesign은 git submodule+npm workspace로 연결 / Measure 기능 비노출 / 로딩 progress bar는 HM 추천 방식으로 신규 연결, 3가지+1가지 질문에 대한 사용자 답변을 바탕으로 계획 수립 후 승인.</div>
</div>
<div class="entry">
<time>17:42</time>
<div><b>1차 구현 커밋</b> — submodule 추가, 워크스페이스 연결, <code>src/viewer2d/*</code> 포팅, React 컴포넌트 전체 신규 작성.</div>
</div>
<div class="entry">
<time>17:46</time>
<div><b>Guard 수정 커밋</b> — <code>ai-generation-guard.mjs</code> REUSE-004 지적(네이티브 <code>&lt;label&gt;</code>)을 HM <code>Label</code>로 교체.</div>
</div>
<div class="entry">
<time>17:53</time>
<div><b>버그 수정 커밋</b> — 샘플 로드 후 캔버스에 아무것도 안 보이는 문제 원인 확정 및 수정 (아래 "발견된 버그" 참고).</div>
</div>
<div class="entry">
<time>18:08</time>
<div><b>이슈 #2 생성</b> — "hmdesign 적용", 이 리포트 작성 및 커밋.</div>
</div>
</div>
<h2>아키텍처: HMDesign 연결</h2>
<p>
<code>@hm-design/ui</code>는 npm 레지스트리에 배포되지 않고 HMDesign 저장소 자신의 npm workspace
(<code>packages/*</code>) 안에서만 resolve된다. <code>exports</code>는 전부 <code>dist/*</code>를
가리키므로 반드시 빌드가 먼저 끝나 있어야 한다.
</p>
<pre><code>git submodule add https://gitea.hmac.kr/b25027/HMDesign.git HMDesign
git submodule update --init --recursive
cd HMDesign &amp;&amp; npm ci &amp;&amp; npm run build:packages &amp;&amp; cd .. # HMDesign 자체 devDependency로 dist/ 생성
npm install # 루트 workspaces(["HMDesign/packages/ui"])가 symlink 연결
npm run dev</code></pre>
<p>
HMDesign 내부 빌드는 HMDesign 자신의 <code>node_modules</code>(Storybook/Tailwind/TS7 등)로 수행하고,
외부 앱(이 저장소)은 빌드된 결과물만 workspace symlink로 소비한다 — 두 트리의 의존성 버전이 섞이지 않는다.
<code>@hm-design/center-graphic</code>은 라이선스 미확정으로 사용하지 않았다.
</p>
<p>
Vite 설정에서 <code>optimizeDeps.entries: ['index.html']</code>을 명시해야 한다 — 지정하지 않으면
Vite의 의존성 스캐너가 submodule(HMDesign) 안의 다른 예제용 <code>*.html</code>까지 크롤링하다
해석할 수 없는 <code>@/...</code> alias에서 죽는다.
</p>
<h2>구현 내용</h2>
<h3>디렉토리 구조</h3>
<pre><code>src/
├── main.tsx, App.tsx, index.css
├── viewer2d/ ← 기존 엔진 전체, 로직 불변, import 경로만 조정
├── lib/
│ ├── optionsStore.ts ← IndexedDB 영속, 그대로 포팅
│ └── propertyFormat.ts← 속성 포맷터만 추출 포팅 (DOM 렌더는 React로 대체)
├── components/ ← Toolbar / LayerPanel / PropertiesPanel / OptionsPanel /
│ DraggablePanel / CoordReadout / LicenseNotice / LoadProgress
└── hooks/
├── useDwgViewer.ts ← Viewer2D 인스턴스 소유 + 파일 로드 파이프라인 + 옵션 동기화
└── useDraggablePanel.ts ← panelUtils.ts 드래그/collapse 로직의 React 훅 포팅</code></pre>
<h3>UI → HMDesign 컴포넌트 매핑 (요약)</h3>
<table>
<tr><th>원본 UI</th><th>HMDesign 매핑</th></tr>
<tr><td>Open file / 샘플 3버튼</td><td><code>Button</code> (+숨김 <code>&lt;input type=file&gt;</code>, HM에 대응 컴포넌트 없음)</td></tr>
<tr><td>Model/Layout 탭</td><td><code>Tabs</code></td></tr>
<tr><td>2.5D·테마 토글</td><td><code>Switch</code> + <code>Label</code></td></tr>
<tr><td>레이어/옵션/속성 패널 열기</td><td><code>Toggle</code></td></tr>
<tr><td>레이어 검색/전체켜기끄기</td><td><code>FilterBar</code> / <code>FilterBarSearch</code> / <code>FilterBarActions</code></td></tr>
<tr><td>레이어 행(색상/개수/가시성)</td><td>커스텀 — color swatch(동적 color), <code>Badge</code>, lucide <code>Eye</code>/<code>EyeOff</code></td></tr>
<tr><td>패널 자체(드래그/접기/리사이즈)</td><td><code>DraggablePanel</code> 커스텀(HM <code>Card</code> 기반 + 포인터 드래그 훅, resize는 네이티브 CSS)</td></tr>
<tr><td>슬라이더+숫자입력 3종</td><td><code>Slider</code> + <code>Input</code></td></tr>
<tr><td>라이선스/크레딧</td><td><code>Dialog</code> (THIRD-PARTY-NOTICES / MPL-2.0 / patch 링크 3종 유지)</td></tr>
<tr><td>상단 로딩바</td><td><code>Progress</code> (원본은 미연결 죽은 코드였음 — HM 추천 방식으로 신규 실연결)</td></tr>
</table>
<p>Navigation(GNB/LNB/Dashboard)은 채용하지 않았다 — 멀티페이지 앱이 아닌 전체화면 캔버스형 단일 도구이기 때문.</p>
<h2>발견된 버그 및 수정</h2>
<div class="card">
<p><b>증상</b>: 샘플 DWG/DXF 3개 버튼 모두 "로드완료" 상태 텍스트는 뜨지만 캔버스에 아무것도 렌더되지 않음.</p>
<p><b>원인 확정</b>: <code>Viewer2D</code>(포팅된 vanilla 엔진)는 dispose/teardown API가 없고 생성자에서
<code>window</code>에 영구 리스너를 걸고 자체 <code>requestAnimationFrame</code> 루프를 시작한다.
React <code>&lt;StrictMode&gt;</code>의 개발 모드 mount→unmount→remount 더블 호출이
<code>Viewer2D</code>를 같은 container에 두 번 생성했고, 첫 인스턴스를 정리할 방법이 없어
캔버스가 중첩/렌더가 꼬였다.</p>
<p><b>수정 완료·미검증</b>: <code>main.tsx</code>에서 <code>&lt;StrictMode&gt;</code> 제거,
<code>useDwgViewer</code> 마운트 effect에서 construction 전에 <code>container.replaceChildren()</code>으로
방어적 클리어 추가. 코드 수정은 했으나 이 세션엔 브라우저 도구가 없어 실제 화면에서 재확인은 못 함 —
사용자 브라우저에서 재검증 필요.</p>
</div>
<h2>검증 상태</h2>
<ul>
<li><span class="badge ok">검증됨</span> npm workspace symlink 연결 (<code>@hm-design/ui</code> resolve 확인)</li>
<li><span class="badge ok">검증됨</span> <code>tsc --noEmit</code> 타입체크 통과</li>
<li><span class="badge ok">검증됨</span> Vite dev 서버에서 전체 모듈 HTTP 200 서빙 확인</li>
<li><span class="badge ok">검증됨</span> <code>ai-generation-guard.mjs</code> FAIL 0건 (NOT VERIFIED 4건은 HM에 대응 컴포넌트 없는 동적 값 — 문서화된 예외)</li>
<li><span class="badge warn">미검증</span> 실제 브라우저 렌더링 (WASM 파싱, Three.js 캔버스, 패널 드래그/접기, IndexedDB 옵션 영속, 390~1440px 반응형) — 이 세션에 브라우저 도구 없음, 컴파일 통과는 동작 검증이 아님</li>
</ul>
<h2>다음 할 일</h2>
<ul>
<li>StrictMode 제거 수정 후 브라우저에서 샘플 3개 재확인</li>
<li>390/768/1280/1440px 반응형 확인</li>
<li>기존 프로젝트 기능 체크리스트와 1:1 대조 (파일 로드, pan/zoom/orbit, Fit+F, 2.5D, 그리드, 줌속도, 선가중치, Model/Layout 탭, 엔티티 선택→속성, 줌투셀렉션, 좌표 readout, 패널 영속, 한글 텍스트, 라이선스 링크, 신규 progress bar)</li>
</ul>
</main>
</body>
</html>