docs/concepts/01-frontend-visibility-gap-diagnosis.md에서 진단한 1순위 보완("아키텍처/기술스택 진입점 하나 추가")을 실제로 구현한 기록.
진단 문서의 결론은 "기능 커버리지는 문제없고, 이미 만든 것들이 발견 가능하지 않다"는 것이었다. 즉 새 API 연동이 필요한 게 아니라, 이미 존재하는 백엔드/인프라 작업(MSA 4개 서비스, Kafka, pgvector/Qdrant, K8s GitOps, RAGAS 평가)을 프론트 화면 안에서 설명하는 화면 하나가 필요했다.
ArchitecturePanel.tsxAnalysisModal.tsx와 같은 중앙 오버레이 모달 패턴을 재사용해 4개 섹션으로 구성했다:
threat-intel-ai-service/docs/concepts/11-tool-calling-node-and-ragas-evaluation.md에 기록된 실측값(faithfulness 0.75박스/화살표는 별도 다이어그램 라이브러리 없이 Box/Arrow 헬퍼 컴포넌트(테두리 div + 텍스트 화살표)로 직접 구현해 기존 터미널 톤(term-fg/term-panel/term-dim 색상 토큰)과 통일했다.
처음엔 헤더에 버튼만 추가했다. 하지만 01번 문서의 핵심 지적이 "처음 보는 사람은 버튼을 눌러봐야만 안다"였다는 걸 떠올리고, 버튼 추가는 근본 해결이 아니라는 결론에 도달했다 — 버튼이 하나 더 늘어난 것뿐, 안 누르면 여전히 안 보인다.
그래서 세션당 최초 1회는 자동으로 열리도록 바꿨다:
useEffect(() => {
if (!window.sessionStorage.getItem("c4i-arch-seen")) setArchOpen(true);
}, []);
function closeArch() {
setArchOpen(false);
window.sessionStorage.setItem("c4i-arch-seen", "1");
}
useState의 lazy initializer에서 바로 sessionStorage를 읽는 방식은 시도하지 않았다 — Next.js는 클라이언트 컴포넌트도 기본적으로 SSR하므로, 서버 렌더 시점(window 없음)과 클라이언트 하이드레이션 시점의 초기값이 달라져 hydration mismatch가 난다. 대신 초기값은 항상 false로 두고 마운트 후 useEffect에서만 판단하도록 분리했다.
eslint-plugin-react-hooks의 react-hooks/set-state-in-effect 규칙이 이 패턴에 걸렸는데, AnalysisModal.tsx가 이미 같은 이유(외부 시스템 동기화)로 인라인 disable 주석을 쓰고 있어 그 컨벤션을 그대로 따랐다.
npx tsc --noEmit, npx eslint src/components/ArchitecturePanel.tsx src/components/Dashboard.tsx — 통과npm run dev 띄운 뒤 실제 브라우저(Claude in Chrome)로 확인: