refactor: 모체 프로젝트와 2D 샘플을 npm workspace 구조로 통합 #5

Open
opened 2026-07-29 08:25:21 +09:00 by lectom · 19 comments

Estimate Time: 6–9 person-days (48–72 hours)
상태: 계획 승인 대기 — 아래 Blocker 해결 전 코드 이관/병합 금지
제안 Milestone: 2D/3D Viewer Integration (Due Date 제안: 2026-08-14, 현재 저장소에는 milestone 없음)
대상: dwg-dxf-viewer-sample + hmwebviewer

Problem Statement

현재 2D 샘플은 표면상 독립 Vite 앱이지만, 재사용 구현·WASM·폰트·샘플 UI를 한 저장소에 복사해 둔 구조입니다. README는 모체의 2D 구현을 다시 복사해 동기화하라고 안내하지만, 현재 모체 저장소에는 해당 구현이나 이력이 없습니다. 두 저장소는 각각 단일 커밋이며 공통 조상도 확인되지 않아 어느 쪽이 원본인지 현재 기록만으로 증명할 수 없습니다.

그 결과 다음 문제가 있습니다.

  • 샘플과 재사용 코어가 분리되지 않아 다른 앱에서 폴더 복사가 필요합니다.
  • 변경 전파와 source of truth가 불명확합니다.
  • 샘플은 Three.js r185 계열, 모체는 r169 계열이라 단순 workspace hoisting이 회귀를 만들 수 있습니다.
  • unit, integration, E2E, CI가 없고 현재 동작을 고정하는 회귀 기준도 없습니다.
  • 동일한 정적 문서가 두 위치에 복제되어 수동 drift 가능성이 있습니다.
  • DWG WASM의 wrapper source, lockfile, 재현 빌드 절차와 LICENSE/NOTICE가 누락되어 있습니다.
  • Viewer2D가 RAF와 전역 listener를 시작하지만 공식 dispose 계약이 없어 앱 전환 시 누수 위험이 있습니다.
  • 절대 asset URL과 Vite 전용 WASM URL 처리 때문에 sub-path 배포 계약이 불명확합니다.

Solution

정본 저장소를 먼저 확정한 뒤, 그 저장소를 단일 lockfile의 npm workspaces monorepo로 전환합니다. 목표 구조는 다음 세 개의 독립 workspace unit입니다.

  1. 기존 3D viewer app
  2. DWG/DXF sample app
  3. 재사용 가능한 @hmwebviewer/viewer2d package

샘플 앱은 package의 public API만 소비하고 내부 모듈을 상대 import하지 않습니다. package는 parser, 공통 parse result, renderer lifecycle을 감추고 create/load/dispose 중심의 깊은 interface를 제공합니다. WASM과 font URL은 명시적으로 주입하거나 package asset contract로 해결하며, root가 정확히 하나의 Three.js 버전과 인스턴스를 소유합니다.

기존 샘플 저장소는 수동 복사로 합치지 않고 이력을 보존해 import합니다. 새 구조가 동일 동작과 배포를 통과한 뒤에도 삭제하지 않고 마지막 standalone tag와 새 위치를 안내하는 read-only archive로 유지합니다.

구현 전 Blocker

  • cyhan fork와 upstream 중 authoritative repository 및 변경 전파 정책을 결정합니다.
  • 현재 2D 구현의 실제 출처, 원본 commit, 이전 parser 교체 기록을 복구합니다.
  • DWG WASM의 upstream/version/변경 여부/대응 Source Form과 NanumGothic·fixture 재배포 근거를 확인하고 최소 LICENSE/NOTICE를 마련합니다.
  • Three.js exact version, peer dependency, bundle dedupe 정책을 정하고 2D/3D 호환성 matrix로 검증합니다. r169 exact pin을 1차 후보로 검증하되 결과 없이 확정하지 않습니다.
  • dispose()가 RAF, window/DOM listener, controls, renderer, geometry, material, texture를 해제하는 lifecycle contract를 정의합니다.
  • 외부 URL과 CAD 입력에 허용 scheme/origin, 최대 크기, timeout/abort, parser error isolation 정책을 정합니다.
  • 의미 있는 기능 RED를 먼저 기록합니다. 의존성 미설치나 compile 실패는 RED 증거로 인정하지 않습니다.

Commits

아래 각 단계는 작업 트리에서 RED를 먼저 확인하고 이슈 코멘트에 명령과 실패 로그를 남긴 뒤 최소 변경으로 GREEN을 만들며, 커밋 시점에는 전체 저장소가 동작해야 합니다.

  1. docs: authoritative repository와 migration policy를 기록합니다. 정본, upstream/fork 동기화, 단일 쓰기, rollback, 기존 저장소 archive 조건을 결정합니다.
  2. test: 2D unit test harness와 characterization test를 추가합니다. 확장자 판별, DXF decoding, adapter의 의미적 불변식을 기존 동작 기준으로 고정합니다.
  3. test: parser integration baseline을 추가합니다. 고정 DWG/DXF fixture가 공통 parse result를 생성하고 주요 entity/layer 통계를 보존하는지 검증합니다.
  4. test: standalone 2D E2E baseline을 추가합니다. URL, file, drop, fit, layer, theme, selection, 오류 상태와 DWG/DXF snapshot을 고정합니다.
  5. test: 기존 3D app 회귀 baseline을 자동화합니다. 현재 smoke 시나리오와 대표 포맷을 workspace 전환 전 GREEN으로 고정합니다.
  6. chore: root workspace shell을 도입합니다. 기존 3D build와 dev 명령을 그대로 유지하면서 root 공통 명령과 Node/npm 버전을 고정합니다.
  7. refactor: 기존 3D app을 app workspace로 등록합니다. 동작·배포 URL·bundle을 바꾸지 않고 root 명령으로 동일하게 실행되게 합니다.
  8. chore: 샘플의 standalone 이력을 보존해 import합니다. 원본 URL, commit SHA, 주요 binary checksum과 rollback tag를 기록합니다.
  9. test: 의도한 viewer2d package consumer contract를 먼저 RED로 추가합니다. sample app이 package name만 import하는 contract가 package 부재로 실패하는 것을 확인합니다.
  10. refactor: viewer2d public facade의 최소 shell을 추가해 contract를 GREEN으로 만듭니다. 아직 내부 구현 이동 없이 interface와 error contract만 고정합니다.
  11. refactor: 공통 type, extension detection, decoding을 package 내부로 옮깁니다. 각 이동 뒤 unit/integration을 GREEN으로 유지합니다.
  12. refactor: DXF parser와 adapter chain을 package 내부로 옮깁니다. 실제 fixture 결과가 기존 baseline과 동일해야 합니다.
  13. refactor: DWG parser와 고정 checksum WASM을 package 내부로 옮깁니다. 출처와 라이선스 Blocker가 해결된 범위에서만 진행합니다.
  14. refactor: renderer와 text engine을 package 내부로 옮깁니다. host UI가 내부 Three.js 객체나 font 전역을 알 필요가 없도록 facade를 완성합니다.
  15. fix: viewer lifecycle disposal을 구현합니다. 10회 이상 2D↔3D mount/unmount 후 RAF, listener, WebGL context와 heap 잔존량을 검증합니다.
  16. build: Three.js와 asset contract를 단일화합니다. exact version/peer dependency/dedupe, WASM MIME, font와 base URL, sub-path production preview를 검증합니다.
  17. refactor: sample app을 package consumer로 전환합니다. 모든 상대 내부 import와 복제 구현을 제거하고 기존 E2E/snapshot을 GREEN으로 유지합니다.
  18. fix: 무신뢰 CAD 입력의 최소 안전 경계를 추가합니다. oversized/malformed fixture와 URL policy의 RED→GREEN 증거를 남깁니다.
  19. ci: 단일 verify 진입점을 추가합니다. clean install, typecheck, build, unit, integration, Chromium E2E와 snapshot을 root에서 실행합니다. Gitea Actions가 비활성이면 동일 명령의 수동 증거를 이슈에 첨부합니다.
  20. docs: 문서와 migration 안내를 단일화합니다. docs를 canonical source로 선언하고 정적 제공본은 build 산출물 또는 drift check 대상으로 만듭니다.
  21. chore: cutover와 rollback을 검증합니다. standalone과 monorepo 결과를 비교하고 tag를 만든 뒤 기존 저장소를 삭제하지 않고 read-only archive로 전환합니다.

Decision Document

  • 지속적인 폴더 복사, git dependency, submodule, 외부 npm publish는 현재 목표에서 제외합니다. history-preserving import는 초기 이관에만 사용합니다.
  • package와 sample app은 분리합니다. package는 host UI, toolbar, sample button을 소유하지 않습니다.
  • public interface는 viewer 생성, CAD 입력 load, lifecycle dispose, 오류/상태 callback에 집중합니다.
  • parser와 renderer를 하나의 package로 시작하되 내부 경계는 숨깁니다. 추가 package 분리는 사용 사례가 생길 때 별도 결정합니다.
  • Three.js는 package의 peer dependency, app/root의 exact dependency로 단일 인스턴스를 보장하는 방향을 우선 검증합니다.
  • WASM은 Vite 전용 package인지 bundler-neutral package인지 먼저 명시하며, font와 WASM 초기화 책임을 API contract에 포함합니다.
  • docs를 canonical로 사용합니다. Wiki는 현재 404로 비활성이므로 이번 이슈에서 활성화하지 않습니다.
  • 기존 샘플 저장소는 archive만 하고 삭제하지 않습니다.

Testing Decisions

좋은 테스트는 내부 함수 배치가 아니라 사용자가 관찰하는 파싱 결과, 렌더 상태, 오류, lifecycle, 배포 가능성을 검증해야 합니다.

  • Unit: extension/dispatch, encoding decode, adapter semantic invariants, input policy
  • Integration: 실제 DWG/WASM과 DXF fixture에서 공통 parse result, lazy initialization, asset URL, font loading
  • E2E: 기존 3D 대표 포맷과 DWG/DXF의 URL/file/drop, fit, layer, theme, selection, 오류, 2D↔3D 전환
  • Visual: 고정 fixture, viewport, DPR=1, font/WebGL 안정화 후 snapshot을 생성하고 GPU 차이 tolerance와 DOM assertion을 병행합니다.
  • Deployment: root와 sub-path에서 production preview를 띄워 WASM/font/sample 응답과 MIME/CSP를 확인합니다.
  • Lifecycle/Performance: parse-to-first-render, peak heap, WebGL context, 10회 전환 후 RAF/listener 잔존량을 기록합니다.
  • 증거: UI 변경 snapshot은 Gitea attachment API로 업로드하고 이슈 코멘트에 이미지로 표시합니다.

Acceptance Criteria

  • 모든 Blocker가 결정 또는 검증 증거로 닫혔습니다.
  • sample app은 package name만 import하고 복제된 viewer implementation이 없습니다.
  • 2D와 3D가 단일 Three.js runtime을 사용하며 양쪽 회귀 테스트가 통과합니다.
  • clean install부터 unit/integration/E2E/build까지 단일 verify 명령이 성공합니다.
  • DWG/DXF와 기존 3D snapshot 및 attachment가 이슈에 기록됩니다.
  • root/sub-path 배포에서 WASM, font, samples가 정상 로드됩니다.
  • 반복 전환 후 RAF/listener/WebGL resource 누수가 기준 이내입니다.
  • license/notice와 source provenance가 검토되었습니다.
  • canonical docs와 생성 산출물의 drift가 방지됩니다.
  • standalone rollback tag가 있고 기존 저장소는 삭제되지 않습니다.

Out of Scope

다음 항목은 이 이슈의 완료를 막지 않으며 별도 이슈로 관리합니다.

Further Notes

RED팀 3개 관점(이력/이관, package architecture, 테스트/운영)이 본 계획을 검토했습니다. 공통 판정은 계획 이슈 생성은 통과, 구현과 기존 저장소 종료는 Blocker 해결 전 보류입니다.

현재 환경에는 sample 의존성이 설치되어 있지 않아 typecheck가 tsc: not found로 종료했습니다. 이는 기능 RED 증거가 아니며, 실제 구현 시작 시 clean install 이후 의미 있는 행동 실패를 별도로 기록해야 합니다.

> **Estimate Time:** 6–9 person-days (48–72 hours) > **상태:** 계획 승인 대기 — 아래 Blocker 해결 전 코드 이관/병합 금지 > **제안 Milestone:** `2D/3D Viewer Integration` (Due Date 제안: 2026-08-14, 현재 저장소에는 milestone 없음) > **대상:** [dwg-dxf-viewer-sample](https://gitea.hmac.kr/cyhan/dwg-dxf-viewer-sample) + [hmwebviewer](https://gitea.hmac.kr/cyhan/hmwebviewer) ## Problem Statement 현재 2D 샘플은 표면상 독립 Vite 앱이지만, 재사용 구현·WASM·폰트·샘플 UI를 한 저장소에 복사해 둔 구조입니다. README는 모체의 2D 구현을 다시 복사해 동기화하라고 안내하지만, 현재 모체 저장소에는 해당 구현이나 이력이 없습니다. 두 저장소는 각각 단일 커밋이며 공통 조상도 확인되지 않아 어느 쪽이 원본인지 현재 기록만으로 증명할 수 없습니다. 그 결과 다음 문제가 있습니다. - 샘플과 재사용 코어가 분리되지 않아 다른 앱에서 폴더 복사가 필요합니다. - 변경 전파와 source of truth가 불명확합니다. - 샘플은 Three.js r185 계열, 모체는 r169 계열이라 단순 workspace hoisting이 회귀를 만들 수 있습니다. - unit, integration, E2E, CI가 없고 현재 동작을 고정하는 회귀 기준도 없습니다. - 동일한 정적 문서가 두 위치에 복제되어 수동 drift 가능성이 있습니다. - DWG WASM의 wrapper source, lockfile, 재현 빌드 절차와 LICENSE/NOTICE가 누락되어 있습니다. - Viewer2D가 RAF와 전역 listener를 시작하지만 공식 dispose 계약이 없어 앱 전환 시 누수 위험이 있습니다. - 절대 asset URL과 Vite 전용 WASM URL 처리 때문에 sub-path 배포 계약이 불명확합니다. ## Solution 정본 저장소를 먼저 확정한 뒤, 그 저장소를 단일 lockfile의 npm workspaces monorepo로 전환합니다. 목표 구조는 다음 세 개의 독립 workspace unit입니다. 1. 기존 3D viewer app 2. DWG/DXF sample app 3. 재사용 가능한 `@hmwebviewer/viewer2d` package 샘플 앱은 package의 public API만 소비하고 내부 모듈을 상대 import하지 않습니다. package는 parser, 공통 parse result, renderer lifecycle을 감추고 `create/load/dispose` 중심의 깊은 interface를 제공합니다. WASM과 font URL은 명시적으로 주입하거나 package asset contract로 해결하며, root가 정확히 하나의 Three.js 버전과 인스턴스를 소유합니다. 기존 샘플 저장소는 수동 복사로 합치지 않고 이력을 보존해 import합니다. 새 구조가 동일 동작과 배포를 통과한 뒤에도 삭제하지 않고 마지막 standalone tag와 새 위치를 안내하는 read-only archive로 유지합니다. ### 구현 전 Blocker - [ ] `cyhan` fork와 upstream 중 authoritative repository 및 변경 전파 정책을 결정합니다. - [ ] 현재 2D 구현의 실제 출처, 원본 commit, 이전 parser 교체 기록을 복구합니다. - [ ] DWG WASM의 upstream/version/변경 여부/대응 Source Form과 NanumGothic·fixture 재배포 근거를 확인하고 최소 LICENSE/NOTICE를 마련합니다. - [ ] Three.js exact version, peer dependency, bundle dedupe 정책을 정하고 2D/3D 호환성 matrix로 검증합니다. r169 exact pin을 1차 후보로 검증하되 결과 없이 확정하지 않습니다. - [ ] `dispose()`가 RAF, window/DOM listener, controls, renderer, geometry, material, texture를 해제하는 lifecycle contract를 정의합니다. - [ ] 외부 URL과 CAD 입력에 허용 scheme/origin, 최대 크기, timeout/abort, parser error isolation 정책을 정합니다. - [ ] 의미 있는 기능 RED를 먼저 기록합니다. 의존성 미설치나 compile 실패는 RED 증거로 인정하지 않습니다. ## Commits 아래 각 단계는 작업 트리에서 RED를 먼저 확인하고 이슈 코멘트에 명령과 실패 로그를 남긴 뒤 최소 변경으로 GREEN을 만들며, 커밋 시점에는 전체 저장소가 동작해야 합니다. 1. **docs: authoritative repository와 migration policy를 기록합니다.** 정본, upstream/fork 동기화, 단일 쓰기, rollback, 기존 저장소 archive 조건을 결정합니다. 2. **test: 2D unit test harness와 characterization test를 추가합니다.** 확장자 판별, DXF decoding, adapter의 의미적 불변식을 기존 동작 기준으로 고정합니다. 3. **test: parser integration baseline을 추가합니다.** 고정 DWG/DXF fixture가 공통 parse result를 생성하고 주요 entity/layer 통계를 보존하는지 검증합니다. 4. **test: standalone 2D E2E baseline을 추가합니다.** URL, file, drop, fit, layer, theme, selection, 오류 상태와 DWG/DXF snapshot을 고정합니다. 5. **test: 기존 3D app 회귀 baseline을 자동화합니다.** 현재 smoke 시나리오와 대표 포맷을 workspace 전환 전 GREEN으로 고정합니다. 6. **chore: root workspace shell을 도입합니다.** 기존 3D build와 dev 명령을 그대로 유지하면서 root 공통 명령과 Node/npm 버전을 고정합니다. 7. **refactor: 기존 3D app을 app workspace로 등록합니다.** 동작·배포 URL·bundle을 바꾸지 않고 root 명령으로 동일하게 실행되게 합니다. 8. **chore: 샘플의 standalone 이력을 보존해 import합니다.** 원본 URL, commit SHA, 주요 binary checksum과 rollback tag를 기록합니다. 9. **test: 의도한 viewer2d package consumer contract를 먼저 RED로 추가합니다.** sample app이 package name만 import하는 contract가 package 부재로 실패하는 것을 확인합니다. 10. **refactor: viewer2d public facade의 최소 shell을 추가해 contract를 GREEN으로 만듭니다.** 아직 내부 구현 이동 없이 interface와 error contract만 고정합니다. 11. **refactor: 공통 type, extension detection, decoding을 package 내부로 옮깁니다.** 각 이동 뒤 unit/integration을 GREEN으로 유지합니다. 12. **refactor: DXF parser와 adapter chain을 package 내부로 옮깁니다.** 실제 fixture 결과가 기존 baseline과 동일해야 합니다. 13. **refactor: DWG parser와 고정 checksum WASM을 package 내부로 옮깁니다.** 출처와 라이선스 Blocker가 해결된 범위에서만 진행합니다. 14. **refactor: renderer와 text engine을 package 내부로 옮깁니다.** host UI가 내부 Three.js 객체나 font 전역을 알 필요가 없도록 facade를 완성합니다. 15. **fix: viewer lifecycle disposal을 구현합니다.** 10회 이상 2D↔3D mount/unmount 후 RAF, listener, WebGL context와 heap 잔존량을 검증합니다. 16. **build: Three.js와 asset contract를 단일화합니다.** exact version/peer dependency/dedupe, WASM MIME, font와 base URL, sub-path production preview를 검증합니다. 17. **refactor: sample app을 package consumer로 전환합니다.** 모든 상대 내부 import와 복제 구현을 제거하고 기존 E2E/snapshot을 GREEN으로 유지합니다. 18. **fix: 무신뢰 CAD 입력의 최소 안전 경계를 추가합니다.** oversized/malformed fixture와 URL policy의 RED→GREEN 증거를 남깁니다. 19. **ci: 단일 verify 진입점을 추가합니다.** clean install, typecheck, build, unit, integration, Chromium E2E와 snapshot을 root에서 실행합니다. Gitea Actions가 비활성이면 동일 명령의 수동 증거를 이슈에 첨부합니다. 20. **docs: 문서와 migration 안내를 단일화합니다.** docs를 canonical source로 선언하고 정적 제공본은 build 산출물 또는 drift check 대상으로 만듭니다. 21. **chore: cutover와 rollback을 검증합니다.** standalone과 monorepo 결과를 비교하고 tag를 만든 뒤 기존 저장소를 삭제하지 않고 read-only archive로 전환합니다. ## Decision Document - 지속적인 폴더 복사, git dependency, submodule, 외부 npm publish는 현재 목표에서 제외합니다. history-preserving import는 초기 이관에만 사용합니다. - package와 sample app은 분리합니다. package는 host UI, toolbar, sample button을 소유하지 않습니다. - public interface는 viewer 생성, CAD 입력 load, lifecycle dispose, 오류/상태 callback에 집중합니다. - parser와 renderer를 하나의 package로 시작하되 내부 경계는 숨깁니다. 추가 package 분리는 사용 사례가 생길 때 별도 결정합니다. - Three.js는 package의 peer dependency, app/root의 exact dependency로 단일 인스턴스를 보장하는 방향을 우선 검증합니다. - WASM은 Vite 전용 package인지 bundler-neutral package인지 먼저 명시하며, font와 WASM 초기화 책임을 API contract에 포함합니다. - docs를 canonical로 사용합니다. Wiki는 현재 404로 비활성이므로 이번 이슈에서 활성화하지 않습니다. - 기존 샘플 저장소는 archive만 하고 삭제하지 않습니다. ## Testing Decisions 좋은 테스트는 내부 함수 배치가 아니라 사용자가 관찰하는 파싱 결과, 렌더 상태, 오류, lifecycle, 배포 가능성을 검증해야 합니다. - **Unit:** extension/dispatch, encoding decode, adapter semantic invariants, input policy - **Integration:** 실제 DWG/WASM과 DXF fixture에서 공통 parse result, lazy initialization, asset URL, font loading - **E2E:** 기존 3D 대표 포맷과 DWG/DXF의 URL/file/drop, fit, layer, theme, selection, 오류, 2D↔3D 전환 - **Visual:** 고정 fixture, viewport, DPR=1, font/WebGL 안정화 후 snapshot을 생성하고 GPU 차이 tolerance와 DOM assertion을 병행합니다. - **Deployment:** root와 sub-path에서 production preview를 띄워 WASM/font/sample 응답과 MIME/CSP를 확인합니다. - **Lifecycle/Performance:** parse-to-first-render, peak heap, WebGL context, 10회 전환 후 RAF/listener 잔존량을 기록합니다. - **증거:** UI 변경 snapshot은 Gitea attachment API로 업로드하고 이슈 코멘트에 이미지로 표시합니다. ## Acceptance Criteria - [ ] 모든 Blocker가 결정 또는 검증 증거로 닫혔습니다. - [ ] sample app은 package name만 import하고 복제된 viewer implementation이 없습니다. - [ ] 2D와 3D가 단일 Three.js runtime을 사용하며 양쪽 회귀 테스트가 통과합니다. - [ ] clean install부터 unit/integration/E2E/build까지 단일 verify 명령이 성공합니다. - [ ] DWG/DXF와 기존 3D snapshot 및 attachment가 이슈에 기록됩니다. - [ ] root/sub-path 배포에서 WASM, font, samples가 정상 로드됩니다. - [ ] 반복 전환 후 RAF/listener/WebGL resource 누수가 기준 이내입니다. - [ ] license/notice와 source provenance가 검토되었습니다. - [ ] canonical docs와 생성 산출물의 drift가 방지됩니다. - [ ] standalone rollback tag가 있고 기존 저장소는 삭제되지 않습니다. ## Out of Scope 다음 항목은 이 이슈의 완료를 막지 않으며 별도 이슈로 관리합니다. - [#1 Viewer2D 전면 TypeScript/deep module 현대화](https://gitea.hmac.kr/cyhan/dwg-dxf-viewer-sample/issues/1) - [#2 Three.js r169 이후 업그레이드](https://gitea.hmac.kr/cyhan/dwg-dxf-viewer-sample/issues/2) - [#3 DWG WASM 재현 빌드·SBOM·외부 배포 자동화](https://gitea.hmac.kr/cyhan/dwg-dxf-viewer-sample/issues/3) — 단, 최소 출처·라이선스 확인은 본 이슈 Blocker입니다. - [#4 2D/3D 단일 Viewer UI](https://gitea.hmac.kr/cyhan/dwg-dxf-viewer-sample/issues/4) - 2D renderer 성능 최적화, parser 교체, 새 CAD 기능 - 다중 브라우저·모바일 GPU·접근성·국제화 전면 확대 - Wiki 재활성화와 자동 publish ## Further Notes RED팀 3개 관점(이력/이관, package architecture, 테스트/운영)이 본 계획을 검토했습니다. 공통 판정은 **계획 이슈 생성은 통과, 구현과 기존 저장소 종료는 Blocker 해결 전 보류**입니다. 현재 환경에는 sample 의존성이 설치되어 있지 않아 typecheck가 `tsc: not found`로 종료했습니다. 이는 기능 RED 증거가 아니며, 실제 구현 시작 시 clean install 이후 의미 있는 행동 실패를 별도로 기록해야 합니다.
Author

구현 시작 — 2026-07-29

  • main 브랜치를 master 기준으로 생성하고 체크아웃했습니다.
  • 현재 저장소를 정본으로 삼아 모체 3D 앱과 2D 샘플/재사용 package를 한 npm workspaces 저장소로 통합하는 방향으로 진행합니다.
  • 기존 fixture인 BasicSample.dwg, simple.dxf로 characterization test를 먼저 구축합니다.
  • Three.js는 npm registry에서 확인한 최신 stable exact version으로 2D/3D 모두 통일하되, 변경 전 RED와 기존 3D 포맷 회귀를 함께 기록합니다.
  • 구현 순서는 parser/unit → package integration → standalone 2D E2E → 3D 회귀 → lifecycle/sub-path/security → 전체 검증입니다.

코드 변경 전 기능 RED 결과와 구체 구현 방법을 다음 코멘트로 남기겠습니다.

## 구현 시작 — 2026-07-29 - `main` 브랜치를 `master` 기준으로 생성하고 체크아웃했습니다. - 현재 저장소를 정본으로 삼아 모체 3D 앱과 2D 샘플/재사용 package를 한 npm workspaces 저장소로 통합하는 방향으로 진행합니다. - 기존 fixture인 `BasicSample.dwg`, `simple.dxf`로 characterization test를 먼저 구축합니다. - Three.js는 npm registry에서 확인한 최신 stable exact version으로 2D/3D 모두 통일하되, 변경 전 RED와 기존 3D 포맷 회귀를 함께 기록합니다. - 구현 순서는 parser/unit → package integration → standalone 2D E2E → 3D 회귀 → lifecycle/sub-path/security → 전체 검증입니다. 코드 변경 전 기능 RED 결과와 구체 구현 방법을 다음 코멘트로 남기겠습니다.
Author

RED 1 — 재사용 package consumer contract

명령:

npm run test:unit

관찰된 실패:

FAIL tests/viewer2d.contract.test.ts
Error: Cannot find package '@hmwebviewer/viewer2d'
Test Files 1 failed

이는 의존성 설치/compile 실패가 아니라, 샘플이 사용할 재사용 package interface가 실제로 존재하지 않는 기능 RED입니다.

GREEN 구현 방법

  1. 기존 2D parser/renderer 구현을 @hmwebviewer/viewer2d workspace package로 이동합니다.
  2. package interface는 isCad2DFile, parseCad, createViewer2D(...).load()/dispose()를 제공합니다.
  3. 첫 vertical slice에서는 실제 simple.dxf를 public interface로 파싱하여 고정된 6개 entity와 layer를 반환하게 합니다.
  4. 이후 DWG/WASM과 browser lifecycle을 각각 별도 RED→GREEN slice로 확장합니다.
## RED 1 — 재사용 package consumer contract 명령: ```text npm run test:unit ``` 관찰된 실패: ```text FAIL tests/viewer2d.contract.test.ts Error: Cannot find package '@hmwebviewer/viewer2d' Test Files 1 failed ``` 이는 의존성 설치/compile 실패가 아니라, 샘플이 사용할 재사용 package interface가 실제로 존재하지 않는 기능 RED입니다. ### GREEN 구현 방법 1. 기존 2D parser/renderer 구현을 `@hmwebviewer/viewer2d` workspace package로 이동합니다. 2. package interface는 `isCad2DFile`, `parseCad`, `createViewer2D(...).load()/dispose()`를 제공합니다. 3. 첫 vertical slice에서는 실제 `simple.dxf`를 public interface로 파싱하여 고정된 6개 entity와 layer를 반환하게 합니다. 4. 이후 DWG/WASM과 browser lifecycle을 각각 별도 RED→GREEN slice로 확장합니다.
Author

GREEN 1 — @hmwebviewer/viewer2d public package contract

구현:

  • 기존 2D 구현을 workspace package로 이동했습니다.
  • public interface에 isCad2DFile, parseCad, createViewer2D를 추가했습니다.
  • 샘플 앱은 내부 parser/renderer 상대 import 대신 package name을 사용합니다.
  • Three.js와 @types/three는 최신 stable 0.185.1 단일 버전으로 exact pin했습니다.
  • Vite 8.1.5로 올려 npm audit의 high/moderate 취약점을 제거했습니다.

검증:

npm run test:unit  → 1 passed
npm run typecheck  → exit 0
npm run build      → Vite 8.1.5, 45 modules, exit 0
npm ls three @types/three --all → three 0.185.1 단일/deduped
npm audit → 0 vulnerabilities

다음 RED는 Viewer2D.dispose()가 없어 mount/unmount 후 RAF와 listener를 해제할 수 없는 lifecycle contract입니다.

## GREEN 1 — `@hmwebviewer/viewer2d` public package contract 구현: - 기존 2D 구현을 workspace package로 이동했습니다. - public interface에 `isCad2DFile`, `parseCad`, `createViewer2D`를 추가했습니다. - 샘플 앱은 내부 parser/renderer 상대 import 대신 package name을 사용합니다. - Three.js와 `@types/three`는 최신 stable `0.185.1` 단일 버전으로 exact pin했습니다. - Vite 8.1.5로 올려 npm audit의 high/moderate 취약점을 제거했습니다. 검증: ```text npm run test:unit → 1 passed npm run typecheck → exit 0 npm run build → Vite 8.1.5, 45 modules, exit 0 npm ls three @types/three --all → three 0.185.1 단일/deduped npm audit → 0 vulnerabilities ``` 다음 RED는 `Viewer2D.dispose()`가 없어 mount/unmount 후 RAF와 listener를 해제할 수 없는 lifecycle contract입니다.
Author

RED 2 — monorepo + Three.js r185.1 통합

모체 a717900을 history-preserving subtree로 import하고 2D/3D/package workspace를 구성한 뒤 전체 검사를 실행했습니다.

npm run typecheck

실패:

@hmwebviewer/viewer-2d-sample:
TS7016 opentype.js declaration을 package consumer가 찾지 못함

@hmwebviewer/viewer-3d:
modelLoader.ts: ColladaLoader result is possibly null

추가 RED팀 확인 결과 r185에서 PLYLoader가 double property를 Float64Array로 보존하므로 기존 loader가 이를 그대로 WebGL에 전달하는 의미 회귀도 확인했습니다.

GREEN 구현 방법

  • viewer2d package가 자체 ambient declaration을 consumer에 노출합니다.
  • Collada null result를 명시적 load error로 처리합니다.
  • PLY position/normal/uv/color의 Float64 attribute를 렌더 전 Float32로 정규화합니다.
  • Y-up FBX와 새 Z-up FBX, double PLY, KTX2 fixture를 E2E matrix에 포함합니다.
  • root lock에서 Three.js와 type은 0.185.1 한 벌만 유지합니다.
## RED 2 — monorepo + Three.js r185.1 통합 모체 `a717900`을 history-preserving subtree로 import하고 2D/3D/package workspace를 구성한 뒤 전체 검사를 실행했습니다. ```text npm run typecheck ``` 실패: ```text @hmwebviewer/viewer-2d-sample: TS7016 opentype.js declaration을 package consumer가 찾지 못함 @hmwebviewer/viewer-3d: modelLoader.ts: ColladaLoader result is possibly null ``` 추가 RED팀 확인 결과 r185에서 PLYLoader가 `double` property를 Float64Array로 보존하므로 기존 loader가 이를 그대로 WebGL에 전달하는 의미 회귀도 확인했습니다. ### GREEN 구현 방법 - viewer2d package가 자체 ambient declaration을 consumer에 노출합니다. - Collada null result를 명시적 load error로 처리합니다. - PLY position/normal/uv/color의 Float64 attribute를 렌더 전 Float32로 정규화합니다. - Y-up FBX와 새 Z-up FBX, double PLY, KTX2 fixture를 E2E matrix에 포함합니다. - root lock에서 Three.js와 type은 0.185.1 한 벌만 유지합니다.
Author

GREEN 2 — 표준 npm workspaces + Three.js 0.185.1

  • root orchestration 아래 3D app, 2D sample app, viewer2d package를 독립 workspace로 분리했습니다.
  • 모체 a717900 전체 tree를 history-preserving subtree로 import하고 byte-for-byte 동일성을 확인했습니다.
  • Three.js와 type을 0.185.1 exact/deduped 한 벌로 통일했습니다.
  • r185 Collada null result를 명시적 오류로 처리했습니다.
  • r185 PLY double attribute를 WebGL 전달 전에 Float32로 정규화하고 DoublePrecision.ply fixture를 추가했습니다.
  • Vite 8의 Rolldown manualChunks interface에 맞게 3D build를 수정했습니다.
  • decoder staging이 workspace hoisting을 따라 설치 package를 해석하고 stale decoder를 제거하도록 변경했습니다.
  • 취약한 선택적 gltf-transform 도구 의존성은 기본 설치에서 분리했습니다. 도구는 기존대로 필요 시 설치합니다.

검증:

npm run typecheck → 2D/3D 모두 exit 0
npm run build → 2D/3D 모두 Vite 8.1.5 exit 0
npm ls three @types/three --all → 0.185.1 단일/deduped
npm install / npm audit → 0 vulnerabilities

다음 단계는 실제 브라우저에서 2D/3D fixture matrix와 lifecycle RED→GREEN을 검증하는 것입니다.

## GREEN 2 — 표준 npm workspaces + Three.js 0.185.1 - root orchestration 아래 3D app, 2D sample app, viewer2d package를 독립 workspace로 분리했습니다. - 모체 `a717900` 전체 tree를 history-preserving subtree로 import하고 byte-for-byte 동일성을 확인했습니다. - Three.js와 type을 `0.185.1` exact/deduped 한 벌로 통일했습니다. - r185 Collada null result를 명시적 오류로 처리했습니다. - r185 PLY double attribute를 WebGL 전달 전에 Float32로 정규화하고 `DoublePrecision.ply` fixture를 추가했습니다. - Vite 8의 Rolldown `manualChunks` interface에 맞게 3D build를 수정했습니다. - decoder staging이 workspace hoisting을 따라 설치 package를 해석하고 stale decoder를 제거하도록 변경했습니다. - 취약한 선택적 gltf-transform 도구 의존성은 기본 설치에서 분리했습니다. 도구는 기존대로 필요 시 설치합니다. 검증: ```text npm run typecheck → 2D/3D 모두 exit 0 npm run build → 2D/3D 모두 Vite 8.1.5 exit 0 npm ls three @types/three --all → 0.185.1 단일/deduped npm install / npm audit → 0 vulnerabilities ``` 다음 단계는 실제 브라우저에서 2D/3D fixture matrix와 lifecycle RED→GREEN을 검증하는 것입니다.
Author

RED 3 — Viewer2D lifecycle

실제 Chrome에서 sample DXF를 렌더한 뒤 package instance의 lifecycle을 검사했습니다.

npm run test:e2e -- --grep "releases its canvas"

실패:

Expected { exposed: true, disposable: true }
Received { exposed: false, disposable: false }
1 failed

현재 viewer는 RAF ID를 보관하지 않고 익명 window/canvas/control listener를 등록하며, canvas/WebGL context를 해제하는 dispose() interface가 없습니다.

GREEN 구현 방법

  • RAF callback과 모든 listener를 안정적인 instance field로 보관합니다.
  • dispose()를 멱등으로 구현해 RAF, listener, OrbitControls, scene geometry/material/texture, renderer/context, canvas, CAD result 참조를 해제합니다.
  • onViewChange()는 unsubscribe 함수를 반환합니다.
  • async text generation은 disposal token으로 무효화합니다.
  • sample app이 public viewer instance를 E2E seam으로 노출하고 pagehide에서 정리합니다.
  • dispose 후 canvas 0개와 반복 호출 안전성을 실제 Chrome에서 확인합니다.
## RED 3 — Viewer2D lifecycle 실제 Chrome에서 sample DXF를 렌더한 뒤 package instance의 lifecycle을 검사했습니다. ```text npm run test:e2e -- --grep "releases its canvas" ``` 실패: ```text Expected { exposed: true, disposable: true } Received { exposed: false, disposable: false } 1 failed ``` 현재 viewer는 RAF ID를 보관하지 않고 익명 window/canvas/control listener를 등록하며, canvas/WebGL context를 해제하는 `dispose()` interface가 없습니다. ### GREEN 구현 방법 - RAF callback과 모든 listener를 안정적인 instance field로 보관합니다. - `dispose()`를 멱등으로 구현해 RAF, listener, OrbitControls, scene geometry/material/texture, renderer/context, canvas, CAD result 참조를 해제합니다. - `onViewChange()`는 unsubscribe 함수를 반환합니다. - async text generation은 disposal token으로 무효화합니다. - sample app이 public viewer instance를 E2E seam으로 노출하고 pagehide에서 정리합니다. - dispose 후 canvas 0개와 반복 호출 안전성을 실제 Chrome에서 확인합니다.
Author

RED 4 — 무신뢰 CAD 입력 정책

npx vitest run tests/cad-input-policy.test.ts

실패:

Cannot find module cadInputPolicy
Test Files 1 failed

요구 행동은 same-origin URL만 허용하고, URL/file/response 모두 50 MiB를 초과하면 parsing 전에 거부하는 것입니다.

GREEN에서는 URL resolution, byte limit, AbortController timeout을 작은 input-policy module에 숨기고 sample의 URL/file path가 같은 interface를 사용하도록 연결합니다.

## RED 4 — 무신뢰 CAD 입력 정책 ```text npx vitest run tests/cad-input-policy.test.ts ``` 실패: ```text Cannot find module cadInputPolicy Test Files 1 failed ``` 요구 행동은 same-origin URL만 허용하고, URL/file/response 모두 50 MiB를 초과하면 parsing 전에 거부하는 것입니다. GREEN에서는 URL resolution, byte limit, AbortController timeout을 작은 input-policy module에 숨기고 sample의 URL/file path가 같은 interface를 사용하도록 연결합니다.
Author

GREEN 3 — 실제 Chrome E2E 포맷 매트릭스

  • 실행: npm run test:e2e
  • 결과: 10 passed (11.8s)
  • 2D: DWG 7,309 entities, DXF 6 entities, local upload, cross-origin URL 차단, 50 MiB 초과 차단, idempotent dispose() 검증
  • 3D / Three.js 0.185.1: GLB, Draco GLB, OBJ, FBX, DAE, IFC, Float64 PLY 모두 실제 rendering 성공
  • 각 3D case에서 canvas/model/vertex 존재, finite geometry, Float64 attribute 잔존 없음, pageerror/console error 없음 확인

남은 검증은 production subpath asset 경로, decoder staging 재현성, 전체 clean install 및 evidence attachment입니다.

## GREEN 3 — 실제 Chrome E2E 포맷 매트릭스 - 실행: `npm run test:e2e` - 결과: **10 passed (11.8s)** - 2D: DWG 7,309 entities, DXF 6 entities, local upload, cross-origin URL 차단, 50 MiB 초과 차단, idempotent `dispose()` 검증 - 3D / Three.js `0.185.1`: GLB, Draco GLB, OBJ, FBX, DAE, IFC, Float64 PLY 모두 실제 rendering 성공 - 각 3D case에서 canvas/model/vertex 존재, finite geometry, Float64 attribute 잔존 없음, `pageerror`/console error 없음 확인 남은 검증은 production subpath asset 경로, decoder staging 재현성, 전체 clean install 및 evidence attachment입니다.
Author

RED 5 — production subpath asset 경로

tests/public-asset-url.test.ts를 먼저 추가하고 실행했습니다.

  • 기대: /viewer-3d/ 아래 배포 시 Draco, Basis/KTX2, web-ifc, preview asset도 동일 subpath 아래에서 resolve
  • 실제: runtimeBase public contract가 없어 test suite가 Cannot find module '../apps/viewer-3d/src/runtimeBase'로 실패
  • 기존 코드에서도 /draco/, /basis/, /web-ifc/, /previews/ root-absolute 경로를 확인

구현 방법: import.meta.env.BASE_URL을 한 곳에서 해석하는 publicAssetUrl()을 추가하고 모든 runtime loader/preview 경로를 해당 함수로 전환한 뒤 unit 및 실제 subpath browser test로 GREEN을 확인합니다.

## RED 5 — production subpath asset 경로 `tests/public-asset-url.test.ts`를 먼저 추가하고 실행했습니다. - 기대: `/viewer-3d/` 아래 배포 시 Draco, Basis/KTX2, web-ifc, preview asset도 동일 subpath 아래에서 resolve - 실제: `runtimeBase` public contract가 없어 test suite가 `Cannot find module '../apps/viewer-3d/src/runtimeBase'`로 실패 - 기존 코드에서도 `/draco/`, `/basis/`, `/web-ifc/`, `/previews/` root-absolute 경로를 확인 구현 방법: `import.meta.env.BASE_URL`을 한 곳에서 해석하는 `publicAssetUrl()`을 추가하고 모든 runtime loader/preview 경로를 해당 함수로 전환한 뒤 unit 및 실제 subpath browser test로 GREEN을 확인합니다.
Author

RED 6 — 3D application pagehide lifecycle

실제 Chrome에서 pagehide를 dispatch한 뒤 public exposure와 canvas 정리를 검증했습니다.

  • 기대: { canvases: 0, exposed: false }
  • 실제: { canvases: 0, exposed: true }
  • canvas는 test의 수동 dispose() 호출로 제거되었지만 application 자체가 pagehide에서 viewer를 해제하거나 window.__viewer reference를 제거하지 않았습니다.

구현 방법: application entrypoint에 일회성 pagehide cleanup을 등록하고 dispose() 후 tooling reference를 삭제합니다. 동일 test에서 cleanup 뒤 추가 dispose()가 필요 없는 상태를 GREEN으로 확인합니다.

## RED 6 — 3D application pagehide lifecycle 실제 Chrome에서 `pagehide`를 dispatch한 뒤 public exposure와 canvas 정리를 검증했습니다. - 기대: `{ canvases: 0, exposed: false }` - 실제: `{ canvases: 0, exposed: true }` - canvas는 test의 수동 `dispose()` 호출로 제거되었지만 application 자체가 `pagehide`에서 viewer를 해제하거나 `window.__viewer` reference를 제거하지 않았습니다. 구현 방법: application entrypoint에 일회성 `pagehide` cleanup을 등록하고 `dispose()` 후 tooling reference를 삭제합니다. 동일 test에서 cleanup 뒤 추가 `dispose()`가 필요 없는 상태를 GREEN으로 확인합니다.
Author

RED 7 — decoder staging permission 재현성

tests/decoder-staging.test.ts를 추가해 installed Three.js/web-ifc 원본과 staged WASM의 SHA-256 일치 및 실행 권한 부재를 검증했습니다.

  • byte checksum: 일치
  • 기대 permission: executable bits 0
  • 실제: executable bits 73 (0o111)

npm package 원본 mode를 cpSync/copyFileSync가 그대로 보존하여 public decoder가 executable file로 생성됩니다. staging 후 directory는 0755, 모든 제공 file은 0644로 정규화하도록 수정하고 clean staging 결과를 재검증합니다.

## RED 7 — decoder staging permission 재현성 `tests/decoder-staging.test.ts`를 추가해 installed Three.js/web-ifc 원본과 staged WASM의 SHA-256 일치 및 실행 권한 부재를 검증했습니다. - byte checksum: 일치 - 기대 permission: executable bits `0` - 실제: executable bits `73` (`0o111`) npm package 원본 mode를 `cpSync`/`copyFileSync`가 그대로 보존하여 public decoder가 executable file로 생성됩니다. staging 후 directory는 `0755`, 모든 제공 file은 `0644`로 정규화하도록 수정하고 clean staging 결과를 재검증합니다.
Author

GREEN 4 — subpath, lifecycle, decoder staging

  • publicAssetUrl() unit: 1 passed
  • production /viewer-3d/ build/preview E2E: Draco 및 IFC decoder가 base 아래에서 응답, failed request 없음
  • 3D pagehide lifecycle E2E: canvas 제거와 window.__viewer reference 삭제 통과
  • 2D↔3D 왕복 10회 E2E: 매 단계 canvas 1개, runtime pageerror 0건
  • decoder staging test: installed source와 staged WASM SHA-256 일치, executable bits 0으로 GREEN

첫 전환 test는 2D가 자동 sample을 여는 것으로 잘못 가정해 ready 상태에서 실패했습니다. 실제 public UI 흐름인 Sample DWG click으로 test setup을 바로잡은 뒤 10회 왕복이 통과했습니다.

## GREEN 4 — subpath, lifecycle, decoder staging - `publicAssetUrl()` unit: 1 passed - production `/viewer-3d/` build/preview E2E: Draco 및 IFC decoder가 base 아래에서 응답, failed request 없음 - 3D `pagehide` lifecycle E2E: canvas 제거와 `window.__viewer` reference 삭제 통과 - 2D↔3D 왕복 10회 E2E: 매 단계 canvas 1개, runtime `pageerror` 0건 - decoder staging test: installed source와 staged WASM SHA-256 일치, executable bits 0으로 GREEN 첫 전환 test는 2D가 자동 sample을 여는 것으로 잘못 가정해 `ready` 상태에서 실패했습니다. 실제 public UI 흐름인 `Sample DWG` click으로 test setup을 바로잡은 뒤 10회 왕복이 통과했습니다.
Author

RED 8 — root verify test boundary/type environment

전체 검증에서 두 가지 repository-level failure를 확인했습니다.

  1. npm run typecheck: ImportMeta.env type 부재 (src/runtimeBase.ts)
  2. npm run test:unit: Vitest가 tests/e2e/*.spec.ts까지 수집하여 Playwright의 test()를 잘못 실행, 4 suite 실패

개별 E2E와 unit behavior는 GREEN이지만 root 단일 진입점의 test boundary가 불명확한 구성 문제입니다. 3D tsconfig에 Vite client type을 명시하고, Vitest include를 tests/**/*.test.ts로 제한한 뒤 전체 명령을 다시 실행합니다.

## RED 8 — root verify test boundary/type environment 전체 검증에서 두 가지 repository-level failure를 확인했습니다. 1. `npm run typecheck`: `ImportMeta.env` type 부재 (`src/runtimeBase.ts`) 2. `npm run test:unit`: Vitest가 `tests/e2e/*.spec.ts`까지 수집하여 Playwright의 `test()`를 잘못 실행, 4 suite 실패 개별 E2E와 unit behavior는 GREEN이지만 root 단일 진입점의 test boundary가 불명확한 구성 문제입니다. 3D tsconfig에 Vite client type을 명시하고, Vitest include를 `tests/**/*.test.ts`로 제한한 뒤 전체 명령을 다시 실행합니다.
Author

실제 구동 이미지 증거

고정 viewport 1440×900, DPR 1, 실제 Chrome에서 evidence E2E 3/3 통과 후 Gitea attachment API로 업로드했습니다.

Viewer2D — DWG / 7,309 entities

Viewer2D DWG 7309 entities

Viewer2D — DXF / 6 entities

Viewer2D DXF 6 entities

Viewer3D — Three.js 0.185.1 GLB

Viewer3D Three.js 0.185.1 GLB

Viewer3D — Three.js 0.185.1 double-precision PLY normalization

Viewer3D Three.js 0.185.1 PLY

SHA-256:

  • DWG: aff6acc4458c3d1ca2ce595e9fce1422ed3f8fa450e9321ea46e218a39d99838
  • DXF: 15517fb580844ee0498698b8b22b595eed84b0e90b43d9cd26bed7b02ebf5b8e
  • GLB: 346cd31b8accf4df71b35e5a330cb7c78ad0f2754cf1bec995851e107728de1f
  • PLY: 13fe36425888c91aa59d534b1d5f1e8ed636ec1d8f6c24fa08f7cbb408fa7e3a
## 실제 구동 이미지 증거 고정 viewport `1440×900`, DPR 1, 실제 Chrome에서 evidence E2E 3/3 통과 후 Gitea attachment API로 업로드했습니다. ### Viewer2D — DWG / 7,309 entities ![Viewer2D DWG 7309 entities](https://gitea.hmac.kr/attachments/f4a89e0a-9adc-4d8e-b1ff-0638226b2159) ### Viewer2D — DXF / 6 entities ![Viewer2D DXF 6 entities](https://gitea.hmac.kr/attachments/f1637c21-56b8-4e3f-9856-91e1ab249864) ### Viewer3D — Three.js `0.185.1` GLB ![Viewer3D Three.js 0.185.1 GLB](https://gitea.hmac.kr/attachments/8cf64c4d-10a2-4b34-bad7-97d6707b2f21) ### Viewer3D — Three.js `0.185.1` double-precision PLY normalization ![Viewer3D Three.js 0.185.1 PLY](https://gitea.hmac.kr/attachments/653a5972-7cc9-4434-ba73-46262e6d076e) SHA-256: - DWG: `aff6acc4458c3d1ca2ce595e9fce1422ed3f8fa450e9321ea46e218a39d99838` - DXF: `15517fb580844ee0498698b8b22b595eed84b0e90b43d9cd26bed7b02ebf5b8e` - GLB: `346cd31b8accf4df71b35e5a330cb7c78ad0f2754cf1bec995851e107728de1f` - PLY: `13fe36425888c91aa59d534b1d5f1e8ed636ec1d8f6c24fa08f7cbb408fa7e3a`
Author

최종 clean verify

커밋: cadbd5f fix: resolve workspace integration blockers (#5)

  1. npm ci — 88 packages 재설치, decoder 재staging, 0 vulnerabilities
  2. npm ls three @types/three — 2D app / 3D app / viewer2d package 모두 three@0.185.1 단일 deduped instance, @types/three@0.185.1
  3. npm run verify성공
    • workspace typecheck: 2/2 성공
    • Vitest: 5 files, 6 tests 통과
    • production build: 2D/3D 성공
    • Playwright 실제 Chrome: 16 tests 통과 (29.4s)
    • E2E runner가 2D/3D/subpath preview를 병렬 기동하고 종료 후 세 port가 모두 해제됨
  4. npm audit --audit-level=low — 0 vulnerabilities
  5. git diff --check — 통과

남은 fixture coverage gap은 사용자 제공 대기 중인 redistributable KTX2 GLB와 명시적 Z-up FBX입니다. 기존 FBX 및 Draco GLB는 통과했지만 이 두 edge fixture를 받기 전에는 해당 세부 조합을 검증했다고 주장하지 않습니다.

## 최종 clean verify 커밋: `cadbd5f fix: resolve workspace integration blockers (#5)` 1. `npm ci` — 88 packages 재설치, decoder 재staging, 0 vulnerabilities 2. `npm ls three @types/three` — 2D app / 3D app / viewer2d package 모두 `three@0.185.1` 단일 deduped instance, `@types/three@0.185.1` 3. `npm run verify` — **성공** - workspace typecheck: 2/2 성공 - Vitest: 5 files, 6 tests 통과 - production build: 2D/3D 성공 - Playwright 실제 Chrome: **16 tests 통과 (29.4s)** - E2E runner가 2D/3D/subpath preview를 병렬 기동하고 종료 후 세 port가 모두 해제됨 4. `npm audit --audit-level=low` — 0 vulnerabilities 5. `git diff --check` — 통과 남은 fixture coverage gap은 사용자 제공 대기 중인 redistributable KTX2 GLB와 명시적 Z-up FBX입니다. 기존 FBX 및 Draco GLB는 통과했지만 이 두 edge fixture를 받기 전에는 해당 세부 조합을 검증했다고 주장하지 않습니다.
Author

RED 9 — parser/result seam을 package 내부로 이동

최종 Spec RED 리뷰에서 sample이 parseCadCadParseResult를 직접 조합해 raw viewer.load(result)를 호출하는 얕은 facade가 Blocker로 지적됐습니다.

먼저 public contract test를 loadCad(viewer, input) -> { entityCount }로 변경했습니다.

  • 기대: package가 parse와 render load를 함께 소유하고 sample은 parser result schema를 모름
  • 실제: TypeError: loadCad is not a function

구현 방법: parseCad를 package 내부 함수로 내리고 loadCad가 parse→viewer.load→resize를 수행한 뒤 UI에 필요한 최소 summary만 반환하도록 합니다. sample에서 CadParseResult 및 raw viewer.load 의존을 제거합니다.

## RED 9 — parser/result seam을 package 내부로 이동 최종 Spec RED 리뷰에서 sample이 `parseCad`와 `CadParseResult`를 직접 조합해 raw `viewer.load(result)`를 호출하는 얕은 facade가 Blocker로 지적됐습니다. 먼저 public contract test를 `loadCad(viewer, input) -> { entityCount }`로 변경했습니다. - 기대: package가 parse와 render load를 함께 소유하고 sample은 parser result schema를 모름 - 실제: `TypeError: loadCad is not a function` 구현 방법: `parseCad`를 package 내부 함수로 내리고 `loadCad`가 parse→viewer.load→resize를 수행한 뒤 UI에 필요한 최소 summary만 반환하도록 합니다. sample에서 `CadParseResult` 및 raw `viewer.load` 의존을 제거합니다.
Author

RED 10 — WebGL context lifecycle 계측

RED 리뷰의 지적에 따라 단순 canvas count 대신 requestAnimationFrame, window/canvas listener, 실제 WebGL context 상태를 browser init script로 계측했습니다.

  • Viewer2D: RAF 0, canvas listener 0, keydown/resize 0, WebGL context lost=true로 통과
  • Viewer3D: RAF 0, canvas listener 0, resize 0이지만 **contextsLost: false**로 실패

ThreeDViewer.dispose()renderer.dispose()와 canvas 제거만 수행하고 context를 명시적으로 반환하지 않습니다. idempotent guard와 renderer.forceContextLoss()를 추가해 동일 E2E를 GREEN으로 전환합니다.

## RED 10 — WebGL context lifecycle 계측 RED 리뷰의 지적에 따라 단순 canvas count 대신 `requestAnimationFrame`, window/canvas listener, 실제 WebGL context 상태를 browser init script로 계측했습니다. - Viewer2D: RAF 0, canvas listener 0, keydown/resize 0, WebGL context lost=true로 통과 - Viewer3D: RAF 0, canvas listener 0, resize 0이지만 **`contextsLost: false`**로 실패 `ThreeDViewer.dispose()`는 `renderer.dispose()`와 canvas 제거만 수행하고 context를 명시적으로 반환하지 않습니다. idempotent guard와 `renderer.forceContextLoss()`를 추가해 동일 E2E를 GREEN으로 전환합니다.
Author

GREEN 5 — 최종 RED Blocker 보완

  • facade contract: loadCad(viewer, input) -> { entityCount } unit GREEN. sample에서 parseCad, CadParseResult, raw viewer.load(result) 의존 제거
  • 2D production /viewer-2d/ E2E GREEN: BasicSample.dwg, bundled acadrust WASM, NanumGothic-Regular.ttf가 모두 subpath 아래에서 200 응답하고 7,309 entities rendering
  • 강화 lifecycle E2E GREEN:
    • 2D/3D 모두 active RAF 0
    • window keydown/resize listener 0
    • canvas listener 0
    • canvas 0
    • 실제 WebGL context isContextLost() === true
  • 3D는 RED에서 contextsLost: false를 확인한 뒤 idempotent guard와 renderer.forceContextLoss()를 추가해 GREEN 전환

Heap 절대량은 GC/browser noise 때문에 pass/fail threshold로 두지 않았고, 직접 소유 자원(RAF/listener/context/reference)을 deterministic contract로 검증했습니다.

## GREEN 5 — 최종 RED Blocker 보완 - facade contract: `loadCad(viewer, input) -> { entityCount }` unit GREEN. sample에서 `parseCad`, `CadParseResult`, raw `viewer.load(result)` 의존 제거 - 2D production `/viewer-2d/` E2E GREEN: `BasicSample.dwg`, bundled acadrust WASM, `NanumGothic-Regular.ttf`가 모두 subpath 아래에서 200 응답하고 7,309 entities rendering - 강화 lifecycle E2E GREEN: - 2D/3D 모두 active RAF 0 - window keydown/resize listener 0 - canvas listener 0 - canvas 0 - 실제 WebGL context `isContextLost() === true` - 3D는 RED에서 `contextsLost: false`를 확인한 뒤 idempotent guard와 `renderer.forceContextLoss()`를 추가해 GREEN 전환 Heap 절대량은 GC/browser noise 때문에 pass/fail threshold로 두지 않았고, 직접 소유 자원(RAF/listener/context/reference)을 deterministic contract로 검증했습니다.
Author

보완 커밋 및 verify

커밋: 8ae7c45 fix: close final viewer integration gaps (#5)

npm run verify 최종 결과:

  • typecheck 2/2 성공
  • Vitest 5 files / 6 tests 성공
  • 2D·3D production build 성공
  • 실제 Chrome E2E 17 passed (28.1s)
  • 2D/3D root 및 production subpath server 4개 자동 기동·정리

RED팀 최종 리뷰에서 현재 코드 범위 blocker로 분류한 parser seam, 2D subpath, WebGL context lifecycle은 이 커밋에서 해소했습니다.

## 보완 커밋 및 verify 커밋: `8ae7c45 fix: close final viewer integration gaps (#5)` `npm run verify` 최종 결과: - typecheck 2/2 성공 - Vitest 5 files / 6 tests 성공 - 2D·3D production build 성공 - 실제 Chrome E2E **17 passed (28.1s)** - 2D/3D root 및 production subpath server 4개 자동 기동·정리 RED팀 최종 리뷰에서 현재 코드 범위 blocker로 분류한 parser seam, 2D subpath, WebGL context lifecycle은 이 커밋에서 해소했습니다.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cyhan/dwg-dxf-viewer-sample#5