Skip to content

fix: 컴포넌트 binding audit + 14건 수정 + props 11개 추가#157

Merged
zeakd merged 15 commits into
mainfrom
feat/examples
Jun 3, 2026
Merged

fix: 컴포넌트 binding audit + 14건 수정 + props 11개 추가#157
zeakd merged 15 commits into
mainfrom
feat/examples

Conversation

@zeakd

@zeakd zeakd commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

전체 컴포넌트의 props ↔ SDK 결합 audit + 2회 코드 리뷰 후속 + 외부 패턴 리서치까지 반영한 종합 라운드.

SDK 옵션 저장 패턴(A: _mapOptions 분리 / B: setX 부수효과 / C: _<key>_changed / D: 일방향·static) 정립.

3 commits: 2480ec0 (1차) → f470669 (1차 리뷰 후속 A-D) → 7348f88 (2차 리뷰 후속).

라이브러리 코어

  • useControlledKVO: setter 우선순위 setX > setOptions > set. Map은 옵션을 별도 _mapOptions KVO에 저장하므로 직접 target.set()으론 SDK가 무시. setMapTypeId처럼 부수효과 있는 setX는 우선 사용.
  • useKVO: Map의 옵션 키 구독을 _mapOptions로 라우팅. useKVO(map, 'draggable') 등 정상 발화 수신.
  • kvoEquals: 양쪽 모두 시도 (prev=literal, new=instance 비대칭 해소)

컴포넌트 버그 수정

컴포넌트 변경
NaverMap minZoom/maxZoom static → controlled
NaverMap onInit/onIdle/onTilesloaded useEffectuseLayoutEffect (init 박빙 방지)
NaverMap logoControl 일방향 controlled 분류 정밀화 + dev warn (한 번만, ref 가드)
TrafficLayer autoRefresh undefined 시 endAutoRefresh 호출 안 함
InfoWindow setOptions(obj) 일괄 → per-key useControlledKVO
InfoWindow onOpen/onClose listener를 useLayoutEffect로 (방어적, fix-16)
GroundOverlay SDK 미발화 이벤트(dblclick/mouseover/out/move) 타입 축소
CustomOverlay pane static 명시 + position/anchor kvoEquals 비교

신규 controlled props (11개)

  • NaverMap: cursor, tilt (+onTiltChanged), rotation (+onRotationChanged), tileDuration
  • NaverMap static: repeatX, gl, customStyleId, useStyleMap
  • GroundOverlay: crossOrigin
  • Polygon/Polyline: simplifyLevel

개발자 경험

  • 신규 internal hook useStaticProp: dev/prod 함수 선택 패턴으로 production DCE 시도. undefined grace period로 비동기 prop 패턴({config?.gl}) false positive 방지.
  • DCE 실측 통과: Astro/Vite production 빌드에서 'static prop', 'useStaticPropDev', 한국어 dev warn 모두 0건 ✓
  • 메시지 영문 + 한국어 병기: 외부 라이브러리(downshift/react-aria 등)는 영문만 — 1.0에서 영문 통일 검토

이벤트 등록 시점 정책 (fixes/16)

  • SDK 동기 발화 (init/idle/open/close/tilesloaded): useLayoutEffect로 등록
  • 사용자 인터랙션 (click/drag 등): useEffect 등록 OK
  • audit 결과: 우리 노출 props 모두 적정 처리 (research/bindings/sync-fired-events-audit.md)

회귀 테스트

121 → 160 tests 통과 (25 files, +39 신규).

신규 회귀 테스트:

  • naver-map-controlled.spec.tsx — fix 01/02/05/13/15
  • info-window.spec.tsx 보강 — fix 08, fix-16 timing
  • traffic-layer.spec.tsx 보강 — fix 07
  • event-types.spec.ts 강화 — fix 09 (toEqualTypeOf)
  • custom-overlay.spec.tsx 보강 — fix 10
  • hooks/__tests__/use-kvo.spec.tsx — fix 06
  • hooks/__tests__/use-static-prop.spec.tsx — fix 14 + grace period
  • __tests__/strict-mode.spec.tsx 신규 — StrictMode 더블 마운트 7건
  • __tests__/kvo-equals.spec.ts — 양쪽 비교

외부 패턴 리서치 (related-patterns-research.md)

4개 라이브러리 비교 (@react-google-maps/api, react-map-gl, react-leaflet, @react-three/fiber):

  • react-map-gl과 가장 정렬useIsomorphicLayoutEffect + per-prop diff
  • downshift의 initial-/default- prefix가 가장 깔끔 — 0.3에서 하이브리드 도입 검토
  • useSyncExternalStore caveat 확인: 매 프레임 mutate 시 blocking으로 떨어짐 → 우리 useControlledKVO 재설계(dirty diff) 결정 정당화
  • 0.3 후보: initial- prefix, useKVOFrame (RAF throttle), DCE 가드 표준화
  • 1.0 후보: 영문 통일, Activity 호환성 명시, 번들 검사 CI

Breaking 가능성 (사실상 없음)

변경 영향
GroundOverlay 이벤트 타입 축소 SDK 미발화 이벤트라 실제 영향 0
CustomOverlay pane 변경 무시 명시 이전에도 무시됨

Changeset

patch bump (0.2.0 → 0.2.1).

보류 결정 (decisions/)

  • CompassControl: SDK 번들에 클래스 정의 없음 (legacy stub) — 미노출

🤖 Generated with Claude Code

전체 컴포넌트의 props ↔ SDK 결합 audit 결과를 반영. SDK 옵션 저장 패턴(A: _mapOptions 분리, B: setX 부수효과, C: _<key>_changed, D: 일방향/static)을 정밀화하고, 발견된 버그를 수정하며, 누락 옵션을 노출.

핵심:
- useControlledKVO/useKVO가 Map의 _mapOptions로 정확히 라우팅
- setter 우선순위 setX > setOptions > set (mapTypeId registry 전환 등)
- onInit/onIdle/onTilesloaded을 useLayoutEffect로 이동 (init 발화 누락 방지)
- InfoWindow를 per-key useControlledKVO로 전환 (인라인 객체 폭주 방지)
- TrafficLayer autoRefresh undefined 가드
- GroundOverlay 미발화 이벤트 타입 축소
- CustomOverlay equality + pane static 명시
- logoControl 일방향 controlled 분류 정밀화
- 신규 props: cursor/tilt/rotation/tileDuration/repeatX/gl/customStyleId/useStyleMap/crossOrigin/simplifyLevel
- 신규 internal hook useStaticProp (dev mode warn)

회귀 테스트 121 -> 147개. CompassControl은 SDK 클래스 정의 부재로 보류 결정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zeakd zeakd changed the base branch from dev to main May 2, 2026 14:46
zeakd and others added 10 commits May 3, 2026 00:09
리뷰에서 도출된 6개 Major + 8개 Minor 항목 처리.

Group A (안전한 즉시 수정):
- useStaticProp: dev/prod 함수 선택 패턴으로 production DCE 가능 (effect 슬롯 0)
- kvoEquals: 양쪽 어느 쪽이든 equals 있으면 시도 (literal vs instance 비대칭 해소)
- logoControl false warn: ref 가드로 한 번만 출력 + 회귀 테스트 3건

Group B (측정으로 검증):
- NaverMap 38개 useControlledKVO: 단일 토글 36 get + 8 set, 16ms 내. 이론적 우려 대비 실측 OK
- InfoWindow per-key 동등성: bulk vs per-key 모두 1 draw + 1 resize. SDK _drawRequired 플래그가 중복 draw 방지

Group C (이벤트 등록 시점 정책):
- InfoWindow onOpen/onClose를 useLayoutEffect로 이동. open() 호출 직후 동기 발화하는
  'open' 이벤트를 listener 미등록으로 놓치는 timing bug 해소 (fix-13과 동일 원리)
- 정책 명문화: SDK 동기 발화 = useLayoutEffect, 사용자 인터랙션 = useEffect

Group D (Minor 정리):
- console.warn 메시지 영문 우선 + 한국어 보조 (한영 병기)
- StrictMode 더블 마운트 회귀 테스트 7건 추가
- *Options 런타임 audit (zoomControlOptions 등 5개) — 모두 controlled 동작 확정
- useNaverMapStatic vs satisfies 패턴 정책 명시 (3+ 호출 wrapper, 1-2 satisfies)

테스트: 147 -> 160 (+13). 빌드 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2차 리뷰 결과 처리:

1. fix-16 회귀 테스트 catch 능력 보강 — 분석 결과 솔직히 인정
   - test-utils의 mock InfoWindow.open()이 'open' 이벤트 동기 발화하도록 수정
   - 테스트가 onOpen 호출을 직접 검증
   - 결론: 같은 컴포넌트 내 hook 등록 순서가 React에서 보장되므로 useLayoutEffect/useEffect
     차이가 사실상 없음. fix-16은 방어적 보수책. fixes/16에 한계 + 의도 명시

2. vitest.config.ts headless 잠금
   - PWDEBUG/HEADED 환경변수 명시 차단
   - browser.headless: true + launch.headless: true 이중 명시
   - launch.devtools: false

3. SDK 동기 발화 이벤트 audit (미커밋, research/bindings/sync-fired-events-audit.md)
   - OverlayView의 added/removed/draw 이벤트는 우리 prop으로 미노출 → 영향 없음
   - 모든 노출 props가 적정 timing으로 처리됨 확인

테스트: 25 files, 160 tests 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #152(commit 4a12e35)에서 astro-component-docs의 test 스크립트가 'bun test src/'로
변경되었으나 ci.yml에 bun 설치 단계가 누락되어 CI가 지속적으로 실패. setup-bun@v2 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vitest browser mode가 Playwright Chromium 헤드리스 브라우저를 launch하므로,
'pnpm exec playwright install --with-deps chromium' 단계 추가. 이전엔 호스트 환경에
설치되어 있어서 우회됐을 가능성.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SDK는 GROUND_DOMEVENTS의 click 리스너에서 250ms·5px 이내 연속 클릭을
_getSingleClickType(maps.beautified.js:9014)으로 합성 dblclick으로 trigger한다.
이전 타입 축소 라운드가 이를 미발화로 오판해 onDblclick을 제거했으나,
deobfuscation 재검증 결과 실제 발화하므로 복원한다.
mouseover/mouseout/mousemove 제거는 유지(GROUND_DOMEVENTS 미포함).

- GroundOverlayEvent에 'dblclick' 추가 + 근거 주석
- ground-overlay.tsx onDblclick 리스너/deps 복원
- 5종 이벤트 바인딩 런타임 회귀 테스트 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
event-types.spec.ts는 expectTypeOf/toEqualTypeOf만 쓰는 순수 타입 테스트인데,
vitest.config에 typecheck 설정이 없고 tsconfig가 spec을 exclude해 런타임 no-op로
0 assertion 통과해왔다(dead test). GroundOverlay dblclick 오제거를 잡지 못한 원인.

- vitest.config.ts: test.typecheck 활성화 (event-types.spec.ts 평가)
- tsconfig.test.json: typecheck 전용 (메인 tsconfig는 빌드 위해 spec exclude 유지)
- event-types.spec.ts: dblclick 포함 5종으로 단언 정정

검증: dblclick 임시 제거 시 typecheck가 FAIL 처리함을 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tileDuration: controlled → static. SDK에 setTileDuration/tileDuration_changed가
  없고 타일 생성 시에만 읽히므로(maps.beautified.js:5568/5642/6468) 런타임 변경이
  무시된다. useControlledKVO → useNaverMapStatic으로 이동.
- cursor: SDK가 드래그 중 손 커서로 자체 토글하고 초기화 시 setCursor("open")을
  호출해 controlled 값과 충돌할 수 있음을 JSDoc에 명시.
- TrafficLayer autoRefresh: undefined는 정지가 아니라 'SDK 기본값 위임'이며,
  true→undefined로는 멈추지 않고 false를 명시해야 정지함을 JSDoc에 명시.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
코드 리뷰에서 '수정 전에도 통과 / 얕은 검증'으로 지적된 테스트를 실제 회귀를
잡도록 보강. 각 항목은 대상 코드를 임시로 깨뜨려 FAIL을 확인한 뒤 복원해 검증했다.

- custom-overlay: fromCoordToOffset을 좌표 의존으로 + setPosition spy
  (기존엔 고정 좌표라 draw 미호출에도 통과하는 동어반복)
- info-window: open/close 분기를 toBeDefined 대체 → spy로 호출/인자 단언.
  fix-16 ordering은 hook 선언 순서상 layout/passive 구분 불가라 검증 범위를
  'onOpen 호출 + 정확한 인스턴스/event명 등록'으로 정직하게 하향.
- use-kvo: removeListener no-op mock → 실제 제거 + unmount 후 콜백 미호출 단언
- strict-mode: 더블마운트 발생(toHaveLength(2)) + 첫 인스턴스 cleanup 단언

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- use-controlled-kvo / use-kvo: 읽기 경로와 쓰기/구독 경로의 대상 선택 조건이
  서로 달라 'SDK가 동일 슬롯에 라우팅한다'는 전제에 의존함을 주석화.
  (동작 변경 아님 — 미래 SDK 변경 시 주의점 명시)
- changeset: dblclick 유지/tileDuration static/테스트 카운트 정정
- gitignore: __screenshots__ (vitest 산출물), examples/ (작업 중 — stash 보관,
  별도 브랜치로 분리 예정. lint 오염 방지)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zeakd and others added 4 commits June 3, 2026 20:21
playwright install --with-deps(sudo apt-get install)가 GitHub 호스티드
ubuntu에서 apt 락으로 무한 hang(35분+ 누적 run 다수). 호스티드 러너는
chromium 런타임 라이브러리를 대부분 사전 포함하므로 --with-deps 제거.

- --with-deps 제거 (필요 lib 누락 시 test 단계에서 빠르게 드러남)
- step timeout-minutes: 10 (재발 시 빠른 실패)
- concurrency cancel-in-progress: 중복/hang run 자동 취소

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI의 playwright install이 chromium 다운로드 직후(extract 추정) 무한 hang하는
문제가 반복됐다(--with-deps 무관, 다운로드 자체는 2초). 단위 테스트 171개는
vi.mock 기반이라 real 브라우저가 불필요하므로 happy-dom으로 전환한다.

- vitest.config.ts: browser mode → environment 'happy-dom', smoke 제외
- vitest.browser.config.ts: smoke 전용 분리(실제 SDK 로드 통합 테스트, real 브라우저 필수)
- package.json: test:browser 스크립트 + happy-dom devDep
- ci.yml: playwright install 단계 완전 제거 (hang 원천 차단, CI 고속화)

검증: 기본 test 181 passed(happy-dom) + typecheck OK, test:browser 3 passed(smoke).
smoke 통합 테스트는 로컬/릴리스 시 pnpm test:browser로 검증.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zeakd zeakd merged commit 0127e00 into main Jun 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant