kcg-ai-monitoring/CLAUDE.md
htlee 479a4bfc56 docs: 디자인 시스템 SSOT 개발 지침 + 릴리즈 노트 갱신
CLAUDE.md '디자인 시스템' 섹션 신규:
- 쇼케이스(/design-system.html)를 단일 진실 공급원으로 명시
- 공통 컴포넌트 목록 (Button/Input/Select/PageContainer/PageHeader/Badge 등)
- 카탈로그 API 사용 패턴 (getAlertLevelIntent/Label 등)
- CSS 작성 6대 규칙 (인라인 색상 금지, 하드코딩 Tailwind 색상 금지,
  className override 정책, 시맨틱 토큰 우선, !important 절대 금지,
  vendor prefix 수동 대응)
- 페이지 작성 표준 템플릿
- 접근성 (WCAG 2.1 Level A) 필수 사항
- 변경 사이클 (쇼케이스 → 카탈로그 → 컴포넌트 → 자동 반영)
- 금지 패턴 체크리스트

RELEASE-NOTES.md [Unreleased]에 디자인 시스템 SSOT 작업 항목 추가:
- 쇼케이스 페이지 + 신규 공통 컴포넌트 + 중앙 레지스트리
- 35+ feature 페이지 마이그레이션
- Badge intent 팔레트 테마 분리
- 접근성 전수 처리 (Select TypeScript 강제 등)
2026-04-08 13:29:28 +09:00

249 lines
11 KiB
Markdown
Raw Blame 히스토리

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# KCG AI Monitoring (모노레포)
해양경찰청 AI 기반 불법어선 탐지 및 단속 지원 플랫폼
## 모노레포 구조
```
kcg-ai-monitoring/
├── frontend/ # React 19 + TypeScript + Vite (UI)
├── backend/ # Spring Boot 3.x + Java 21 (인증/권한/감사 + 분석 API)
├── prediction/ # Python 3.9 + FastAPI (AIS 분석 엔진, 5분 주기)
├── database/ # PostgreSQL 마이그레이션 (Flyway V001~V016, 48 테이블)
│ └── migration/
├── deploy/ # 배포 가이드 + 서버 설정 문서
├── docs/ # 프로젝트 문서 (SFR, 아키텍처)
├── .gitea/ # Gitea Actions CI/CD (프론트 자동배포)
├── .claude/ # Claude Code 워크플로우
├── .githooks/ # Git hooks
└── Makefile # 통합 dev/build 명령
```
## 시스템 구성
```
[Frontend Vite :5173] ──→ [Backend Spring :8080] ──→ [PostgreSQL kcgaidb]
↑ write
[Prediction FastAPI :8001] ──────┘ (5분 주기 분석 결과 저장)
↑ read ↑ read
[SNPDB PostgreSQL] (AIS 원본) [Iran Backend] (레거시 프록시, 선택)
```
- **자체 백엔드**: 인증/권한/감사로그/관리자 + 운영자 의사결정 (확정/제외/학습)
- **iran 백엔드 프록시**: 분석 결과 read-only 참조 (vessel_analysis, group_polygons, correlations)
- **신규 DB (kcgaidb)**: 자체 생산 데이터만 저장, prediction 분석 테이블은 미복사
## 명령어
```bash
make install # 전체 의존성 설치
make dev # 프론트 + 백엔드 동시 실행
make dev-all # 프론트 + 백엔드 + prediction 동시 실행
make dev-frontend # 프론트만
make dev-backend # 백엔드만
make dev-prediction # prediction 분석 엔진만 (FastAPI :8001)
make build # 전체 빌드
make lint # 프론트 lint
make format # 프론트 prettier
```
## 기술 스택
### Frontend (`frontend/`)
- React 19, TypeScript 5.9, Vite 8
- Tailwind CSS 4 + CVA
- MapLibre GL 5 + deck.gl 9 (지도)
- ECharts 6 (차트)
- Zustand 5 (상태관리)
- i18next (ko/en)
- React Router 7
- ESLint 10 + Prettier
### Prediction (`prediction/`) — 분석 엔진
- Python 3.11+, FastAPI, APScheduler
- 14개 알고리즘 (어구 추론, 다크베셀, 스푸핑, 환적, 위험도 등)
- 7단계 분류 파이프라인 (전처리→행동→리샘플→특징→분류→클러스터→계절)
- AIS 원본: SNPDB (5분 증분), 결과: kcgaidb (직접 write)
- prediction과 backend는 DB만 공유 (HTTP 호출 X)
### Backend (`backend/`)
- Spring Boot 3.x + Java 21
- Spring Security + JWT
- PostgreSQL + Flyway
- Caffeine (권한 캐싱)
- 트리 기반 RBAC (wing 패턴)
### Database (`kcgaidb`)
- PostgreSQL
- 사용자: `kcg-app`
- 스키마: `kcg`
## 배포 환경
| 서비스 | 서버 (SSH) | 포트 | 관리 |
|---|---|---|---|
| 프론트엔드 | rocky-211 | nginx 443 | Gitea Actions 자동배포 |
| 백엔드 | rocky-211 | 18080 | `systemctl restart kcg-ai-backend` |
| prediction | redis-211 | 18092 | `systemctl restart kcg-ai-prediction` |
- **URL**: https://kcg-ai-monitoring.gc-si.dev
- **배포 상세**: `deploy/README.md` 참조
- **CI/CD**: `.gitea/workflows/deploy.yml` (프론트만 자동, 백엔드/prediction 수동)
## 권한 체계
좌측 탭(메뉴) = 권한 그룹, 내부 패널/액션 = 자식 자원, CRUD 단위 개별 제어.
상세는 `.claude/plans/vast-tinkering-knuth.md` 참조.
## 팀 컨벤션
- 팀 규칙: `.claude/rules/`
- 커밋: Conventional Commits (한국어), `.githooks/commit-msg` 검증
- pre-commit: `frontend/` 디렉토리 기준 TypeScript + ESLint 검증
## 디자인 시스템 (필수 준수)
프론트엔드 UI는 **`/design-system.html` 쇼케이스를 단일 진실 공급원(SSOT)** 으로 한다.
모든 페이지/컴포넌트는 쇼케이스에 정의된 컴포넌트와 토큰만 사용한다.
### 쇼케이스 진입
- **URL**: https://kcg-ai-monitoring.gc-si.dev/design-system.html (메인 SPA와 별개)
- **소스**: `frontend/design-system.html` + `frontend/src/designSystemMain.tsx` + `frontend/src/design-system/`
- **추적 ID 체계**: `TRK-<카테고리>-<슬러그>` (예: `TRK-BADGE-critical-sm`)
- 호버 시 툴팁, "ID 복사 모드"에서 클릭 시 클립보드 복사
- URL 해시 딥링크: `#trk=TRK-BUTTON-primary-md`
- **단축키 `A`**: 다크/라이트 테마 토글
### 공통 컴포넌트 (반드시 사용)
| 컴포넌트 | 위치 | 용도 |
|---|---|---|
| `Badge` | `@shared/components/ui/badge` | 8 intent × 4 size, **className으로 색상 override 금지** |
| `Button` | `@shared/components/ui/button` | 5 variant × 3 size (primary/secondary/ghost/outline/destructive) |
| `Input` / `Select` / `Textarea` / `Checkbox` / `Radio` | `@shared/components/ui/` | 폼 요소 (Select는 aria-label 타입 강제) |
| `TabBar` / `TabButton` | `@shared/components/ui/tabs` | underline / pill / segmented 3 variant |
| `Card` / `CardHeader` / `CardTitle` / `CardContent` | `@shared/components/ui/card` | 4 variant |
| `PageContainer` | `@shared/components/layout` | 페이지 루트 (size sm/md/lg + fullBleed) |
| `PageHeader` | `@shared/components/layout` | 페이지 헤더 (icon + title + description + demo + actions) |
| `Section` | `@shared/components/layout` | Card + CardHeader + CardTitle + CardContent 단축 |
### 카탈로그 기반 라벨/색상
분류 데이터는 `frontend/src/shared/constants/`의 19+ 카탈로그를 참조한다.
중앙 레지스트리는 `catalogRegistry.ts`이며, 쇼케이스가 자동 열거한다.
```tsx
import { Badge } from '@shared/components/ui/badge';
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
<Badge intent={getAlertLevelIntent(event.level)} size="sm">
{getAlertLevelLabel(event.level, t, lang)}
</Badge>
```
ad-hoc 한글/영문 상태 문자열은 `getStatusIntent()` (statusIntent.ts) 사용.
숫자 위험도는 `getRiskIntent(0~100)` 사용.
### CSS 작성 규칙
1. **인라인 색상 금지**`style={{ backgroundColor: '#ef4444' }}` 같은 정적 색상은 작성 금지
- 예외: 동적 데이터 기반 (`backgroundColor: meta.hex`, progress width `${value}%`)
2. **하드코딩 Tailwind 색상 금지**`bg-red-500/20 text-red-400` 같은 직접 작성 금지
- 반드시 Badge intent 또는 카탈로그 API 호출
3. **className override 정책**
- ✅ 레이아웃/위치 보정: `<Badge intent="info" className="w-full justify-center">`
- ❌ 색상/글자 크기 override: `<Badge intent="info" className="bg-red-500 text-xs">`
4. **시맨틱 토큰 우선**`theme.css @layer utilities`의 토큰 사용
- `text-heading` / `text-label` / `text-hint` / `text-on-vivid` / `text-on-bright`
- `bg-surface-raised` / `bg-surface-overlay` / `bg-card` / `bg-background`
5. **!important 절대 금지** — `cn()` + `tailwind-merge`로 충돌 해결
6. **`-webkit-` 벤더 prefix** — 수동 작성 CSS는 `backdrop-filter` 등 prefix 직접 추가 (Tailwind는 자동)
### 페이지 작성 표준 템플릿
```tsx
import { PageContainer, PageHeader, Section } from '@shared/components/layout';
import { Button } from '@shared/components/ui/button';
import { Badge } from '@shared/components/ui/badge';
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
import { Shield, Plus } from 'lucide-react';
import { useTranslation } from 'react-i18next';
export function MyPage() {
const { t, i18n } = useTranslation('common');
const lang = i18n.language as 'ko' | 'en';
return (
<PageContainer>
<PageHeader
icon={Shield}
iconColor="text-blue-400"
title="페이지 제목"
description="페이지 설명"
actions={
<Button variant="primary" icon={<Plus className="w-4 h-4" />}>
추가
</Button>
}
/>
<Section title="데이터 목록">
<Badge intent={getAlertLevelIntent('HIGH')} size="sm">
{getAlertLevelLabel('HIGH', t, lang)}
</Badge>
</Section>
</PageContainer>
);
}
```
### 접근성 (a11y) 필수
- **`<button>`**: `type="button"` 명시 + 아이콘 전용은 `aria-label` 필수
- **`<input>` / `<textarea>` / `<select>`**: `aria-label` 또는 `<label htmlFor>` 필수
- **`Select` 컴포넌트**: TypeScript union type으로 `aria-label`/`aria-labelledby`/`title` 중 하나 컴파일 타임 강제
- 위반 시 WCAG 2.1 Level A 위반 + axe DevTools 경고
### 변경 사이클
1. 디자인 변경이 필요하면 → **쇼케이스에서 먼저 미세조정** → 시각 검증
2. 카탈로그 라벨/색상 변경 → `shared/constants/*` 또는 `variantMeta.ts`만 수정
3. 컴포넌트 변형 추가 → `lib/theme/variants.ts` CVA에만 추가
4. 실 페이지는 **컴포넌트만 사용**, 변경 시 자동 반영
### 금지 패턴 체크리스트
-`<Badge className="bg-red-500/20 text-red-400">``<Badge intent="critical">`
-`<button className="bg-blue-600 ...">``<Button variant="primary">`
-`<input className="bg-surface ...">``<Input>`
-`<div className="p-5 space-y-4">` 페이지 루트 → `<PageContainer>`
-`-m-4` negative margin 해킹 → `<PageContainer fullBleed>`
-`style={{ color: '#ef4444' }}` 정적 색상 → 시맨틱 토큰 또는 카탈로그
-`!important``cn()` 활용
- ❌ 페이지 내 `const STATUS_COLORS = {...}` 로컬 재정의 → shared/constants 카탈로그
## System Flow 뷰어 (개발 단계용)
- **URL**: https://kcg-ai-monitoring.gc-si.dev/system-flow.html (메인 SPA와 별개)
- **소스**: `frontend/system-flow.html` + `frontend/src/systemFlowMain.tsx` + `frontend/src/flow/`
- **매니페스트**: `frontend/src/flow/manifest/` (10개 카테고리 JSON + meta.json + edges.json)
- **노드 ID 명명**: `<category>.<snake_case>` (예: `output.event_generator`, `ui.parent_review`)
- **딥링크**: `/system-flow.html#node=<node_id>` — 산출문서에서 노드 직접 참조
- **가이드**: `docs/system-flow-guide.md` 참조
### `/version` 스킬 사후 처리 (필수)
`/version` 스킬을 실행하여 새 SemVer 버전이 결정되면, Claude는 이어서 다음 작업을 **자동으로** 수행한다 (`/version` 스킬 자체는 팀 공통 파일이라 직접 수정하지 않음):
1. **manifest 동기화**: `/version`이 결정한 새 버전을 `frontend/src/flow/manifest/meta.json`에 반영
- `version`: 새 SemVer (예: `"1.2.0"`)
- `updatedAt`: 현재 ISO datetime (`new Date().toISOString()`)
- `releaseDate`: 오늘 날짜 (`YYYY-MM-DD`)
2. **같은 커밋에 포함**: `frontend/src/flow/manifest/meta.json``/version` 스킬이 만든 커밋에 amend하거나, `docs: VERSION-HISTORY 갱신 + system-flow manifest 동기화`로 통합 커밋
3. **서버 archive는 CI/CD가 자동 처리**: 별도 작업 불필요. main 머지 후 Gitea Actions가 빌드 + dist 배포 + `/deploy/kcg-ai-monitoring-archive/system-flow/v{version}_{date}/`에 스냅샷 영구 보존
### 노드 ID 안정성
- **노드 ID는 절대 변경 금지** (산출문서가 참조하므로 깨짐)
- 노드 제거 시 `status: 'deprecated'`로 마킹 (1~2 릴리즈 유지 후 삭제)
- 새 노드 추가 시 `status: 'implemented'` 또는 `'planned'`