diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 45c7181..2675c9e 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -4,6 +4,9 @@ ## [Unreleased] +### 수정 +- **모니터링/디자인시스템 런타임 에러 해소** — `/monitoring` 의 `SystemStatusPanel` 에서 `stats.total.toLocaleString()` 호출이 백엔드 응답 shape 이슈로 `stats.total` 이 undefined 일 때 Uncaught TypeError 로 크래시하던 문제 null-safe 로 해소(`stats?.total != null`). `/design-system.html` 의 `CatalogBadges` 가 `PERFORMANCE_STATUS_META` 의 `label: {ko, en}` 객체를 그대로 Badge children 으로 주입해 "Objects are not valid as a React child" 를 던지고 `code` 필드 부재로 key 중복 경고가 함께 뜨던 문제 해소 — `Object.entries` 순회 + `AnyMeta.label` 을 `string | {ko,en}` 로 확장 + getKoLabel/getEnLabel 에 label 객체 케이스 추가 + ### 추가 - **Detection Model Registry DB 스키마 (V034, Phase 1-1)** — prediction 17 탐지 알고리즘을 "명시적 모델 단위" 로 분리하고 프론트엔드에서 파라미터·버전·가중치를 관리할 수 있는 기반 인프라. 테이블 4종(`detection_models` 카탈로그 / `detection_model_dependencies` DAG / `detection_model_versions` 파라미터 스냅샷·라이프사이클·role / `detection_model_run_outputs` 월별 파티션) + 뷰 1개(`v_detection_model_comparison` PRIMARY×SHADOW JOIN). 한 모델을 서로 다른 파라미터로 **동시 실행**(PRIMARY 1 + SHADOW/CHALLENGER N) 지원, ACTIVE×PRIMARY 는 UNIQUE partial index 로 1건 보호. 권한 트리 `ai-operations:detection-models`(nav_sort=250) + ADMIN 5 ops / OPERATOR READ+UPDATE / ANALYST·VIEWER READ. (후속: Phase 1-2 Model Registry + DAG Executor, Phase 2 PoC 5 모델 마이그레이션) diff --git a/frontend/src/design-system/sections/CatalogSection.tsx b/frontend/src/design-system/sections/CatalogSection.tsx index e18a475..0ae5c99 100644 --- a/frontend/src/design-system/sections/CatalogSection.tsx +++ b/frontend/src/design-system/sections/CatalogSection.tsx @@ -11,19 +11,30 @@ import { CATALOG_REGISTRY, type CatalogEntry } from '@shared/constants/catalogRe */ interface AnyMeta { - code: string; + /** 일부 카탈로그는 code 없이 Record key 만 사용 (예: PERFORMANCE_STATUS_META) */ + code?: string; intent?: BadgeIntent; fallback?: { ko: string; en: string }; classes?: string | { bg?: string; text?: string; border?: string }; - label?: string; + /** 문자열 라벨 또는 { ko, en } 객체 라벨 양쪽 지원 */ + label?: string | { ko: string; en: string }; } -function getKoLabel(meta: AnyMeta): string { - return meta.fallback?.ko ?? meta.label ?? meta.code; +function getKoLabel(meta: AnyMeta, fallbackKey: string): string { + if (meta.fallback?.ko) return meta.fallback.ko; + if (meta.label && typeof meta.label === 'object' && 'ko' in meta.label) { + return meta.label.ko; + } + if (typeof meta.label === 'string') return meta.label; + return meta.code ?? fallbackKey; } function getEnLabel(meta: AnyMeta): string | undefined { - return meta.fallback?.en; + if (meta.fallback?.en) return meta.fallback.en; + if (meta.label && typeof meta.label === 'object' && 'en' in meta.label) { + return meta.label.en; + } + return undefined; } function getFallbackClasses(meta: AnyMeta): string | undefined { @@ -55,17 +66,19 @@ function renderBadge(meta: AnyMeta, label: string): ReactNode { } function CatalogBadges({ entry }: { entry: CatalogEntry }) { - const items = Object.values(entry.items) as AnyMeta[]; + // Record key 를 안정적 식별자로 사용 (일부 카탈로그는 meta.code 없음) + const items = Object.entries(entry.items) as [string, AnyMeta][]; return (
- {meta.code}
+ {displayCode}