diff --git a/CLAUDE.md b/CLAUDE.md
index ffe7de2..6f311df 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -202,6 +202,35 @@ deploy/ # systemd + nginx 배포 설정
| **DB** | kcgdb | 211.208.115.83:5432/kcgdb (유저: kcg_app, pw: Kcg2026monitor) |
| **DB** | snpdb | 211.208.115.83:5432/snpdb (유저: snp, pw: snp#8932, 읽기 전용) |
+## 디버그 도구 가이드
+
+### 원칙
+- 디버그/개발 전용 기능은 `import.meta.env.DEV` 가드로 감싸서 **프로덕션 빌드에서 코드 자체가 제거**되도록 구현
+- Vite production 빌드 시 `import.meta.env.DEV = false` → dead code elimination → 번들 미포함
+- 무거운 DB 조회, 통계 계산 등도 DEV 가드 안이면 프로덕션에 영향 없음
+
+### 파일 구조
+- 디버그 컴포넌트: `frontend/src/components/{도메인}/debug/` 디렉토리에 분리
+- 메인 컴포넌트에서는 import + DEV 가드로만 연결:
+```tsx
+import { DebugTool } from './debug/DebugTool';
+const debug = import.meta.env.DEV ? useDebugHook() : null;
+// JSX:
+{debug && }
+```
+
+### 기존 디버그 도구
+| 도구 | 위치 | 기능 |
+|------|------|------|
+| CoordDebugTool | `korea/debug/CoordDebugTool.tsx` | Ctrl+Click 좌표 표시 (DD/DMS, 다중 포인트) |
+
+### 디버그 도구 분류 기준
+다음에 해당하면 디버그 도구로 분류하고, 불확실하면 사용자에게 확인:
+- 개발/검증 목적의 좌표/데이터 표시 도구
+- 프로덕션 사용자에게 불필요한 진단 정보
+- 임시 데이터 시각화, 성능 프로파일링
+- 특정 조건에서만 활성화되는 테스트 기능
+
## 팀 규칙
- 코드 스타일: `.claude/rules/code-style.md`
diff --git a/frontend/src/components/korea/KoreaMap.tsx b/frontend/src/components/korea/KoreaMap.tsx
index a9ade65..e28d316 100644
--- a/frontend/src/components/korea/KoreaMap.tsx
+++ b/frontend/src/components/korea/KoreaMap.tsx
@@ -201,6 +201,10 @@ const FILTER_I18N_KEY: Record = {
cnFishing: 'filters.cnFishingMonitor',
};
+// [DEBUG] 좌표 디버그 도구 — 프로덕션 빌드에서 tree-shaking 제거
+import { useCoordDebug } from './debug/useCoordDebug';
+import { CoordDebugOverlay } from './debug/CoordDebugTool';
+
export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintFeed, currentTime, koreaFilters, transshipSuspects, cableWatchSuspects, dokdoWatchSuspects, dokdoAlerts, vesselAnalysis, groupPolygons, hiddenShipCategories, hiddenNationalities, externalFlyTo, onExternalFlyToDone, opsRoute }: Props) {
const { t } = useTranslation();
const mapRef = useRef(null);
@@ -212,6 +216,8 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
const [selectedFleetData, setSelectedFleetData] = useState(null);
const { fontScale } = useFontScale();
const [zoomLevel, setZoomLevel] = useState(KOREA_MAP_ZOOM);
+ // [DEBUG] 좌표 디버그 — DEV에서만 활성화, 프로덕션 빌드에서 tree-shaking 제거
+ const coordDebug = useCoordDebug(!import.meta.env.DEV);
const zoomRef = useRef(KOREA_MAP_ZOOM);
const handleZoom = useCallback((e: { viewState: { zoom: number } }) => {
const z = Math.floor(e.viewState.zoom);
@@ -606,9 +612,13 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
style={{ width: '100%', height: '100%' }}
mapStyle={MAP_STYLE}
onZoom={handleZoom}
+ onClick={coordDebug ? coordDebug.handleMapClick : undefined}
>
+ {/* [DEBUG] Ctrl+Click 좌표 표시 — 프로덕션에서 제거 */}
+ {coordDebug && }
+
= 0 ? 'N' : 'S') : (dd >= 0 ? 'E' : 'W');
+ const abs = Math.abs(dd);
+ const d = Math.floor(abs);
+ const mFull = (abs - d) * 60;
+ const m = Math.floor(mFull);
+ const s = ((mFull - m) * 60).toFixed(2);
+ return `${d}°${String(m).padStart(2, '0')}′${String(s).padStart(5, '0')}″${dir}`;
+}
+
+export function CoordDebugOverlay({ points, onRemove }: { points: CoordPoint[]; onRemove: (id: number) => void }) {
+ return (
+ <>
+ {points.map(cp => (
+
+
+
+
+
onRemove(cp.id)}
+ closeButton={true}
+ closeOnClick={false}
+ anchor="bottom"
+ offset={[0, -10]}
+ style={{ zIndex: 50 }}
+ >
+
+
+ WGS84 (EPSG:4326)
+
+
DD
+
{cp.lat.toFixed(6)}°N
+
{cp.lng.toFixed(6)}°E
+
DMS
+
{toDMS(cp.lat, 'lat')}
+
{toDMS(cp.lng, 'lng')}
+
+
+
+ ))}
+ >
+ );
+}
diff --git a/frontend/src/components/korea/debug/useCoordDebug.ts b/frontend/src/components/korea/debug/useCoordDebug.ts
new file mode 100644
index 0000000..c8fae87
--- /dev/null
+++ b/frontend/src/components/korea/debug/useCoordDebug.ts
@@ -0,0 +1,30 @@
+/**
+ * [DEBUG] Ctrl+Click 좌표 디버그 훅
+ * - disabled=true 시 noop (프로덕션 빌드에서 dead code 제거)
+ */
+import { useState, useCallback } from 'react';
+import type { MapLayerMouseEvent } from 'react-map-gl/maplibre';
+
+export interface CoordPoint {
+ lat: number;
+ lng: number;
+ id: number;
+}
+
+export function useCoordDebug(disabled = false) {
+ const [points, setPoints] = useState([]);
+
+ const handleMapClick = useCallback((e: MapLayerMouseEvent) => {
+ if (disabled) return;
+ if (e.originalEvent.ctrlKey || e.originalEvent.metaKey) {
+ e.originalEvent.preventDefault();
+ setPoints(prev => [...prev, { lat: e.lngLat.lat, lng: e.lngLat.lng, id: Date.now() }]);
+ }
+ }, [disabled]);
+
+ const removePoint = useCallback((id: number) => {
+ setPoints(prev => prev.filter(p => p.id !== id));
+ }, []);
+
+ return { points, handleMapClick, removePoint, disabled };
+}