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 강제 등)
249 lines
11 KiB
Markdown
249 lines
11 KiB
Markdown
# 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'`
|