블로그 UI/UX 개편 — 그래프 범례·필터 + 카테고리 7분류 + ADR 주제 자동분류 (Phase 5)
요약5 Phase 블로그 개편 완성 — 글 카테고리 7분류·ADR 주제 frontmatter 자동분류(KR SSOT+EN 주입)·그래프 범례/필터(WCAG AA)로 포트폴리오 탐색 UI를 완결했다.
목표
- 기술 블로그를 "포트폴리오형"으로 전환하는 5 Phase 개편(사용자
/goal플랜)의 Phase 5(마지막). ADR 그래프 범례/필터, 글 카테고리 7분류, ADR 주제 frontmatter 자동분류로 포트폴리오 탐색 UI를 완결한다. - 방문자가 블로그 글을 장르별로 걸러 볼 수 있고, ADR을 주제별로 탐색하며, 그래프에서 노드/엣지의 의미를 한 눈에 파악할 수 있게 한다.
- 기존 라우트는 무파괴. KO+EN 동시. Phase 1(Sprint 185) Engineering Editorial 디자인 시스템 위에서 작업한다.
결정
D1. 글 카테고리 2분류 → 7분류 + 동적 탭 렌더 (Herald)
기존 category 2분류(journey/challenge)를 7분류(ai-agent/cicd/architecture/backend/platform/frontend/retrospective)로 교체한다. posts.ts에 VALID_CATEGORIES 상수 + fallback 로직을 두고, category-tabs는 글이 1편 이상 존재하는 카테고리만 동적 렌더(빈 frontend 탭 graceful skip)한다. post-list-with-filter는 availableCategories useMemo로 필터 상태를 관리한다. i18n ko+en 7키. 기존 글 10편×2 locale frontmatter 재분류. check-blog-crosscheck의 VALID_CATEGORIES enum을 7분류로 동기화하고 SSOT 주석을 추가해 향후 enum 불일치를 방지한다.
D2. ADR 주제 frontmatter 자동분류 = KR SSOT + loader EN 주입 (Architect)
ADR 주제 분류를 AdrMeta.topics(배열) frontmatter로 선언하고, loader가 EN locale ADR의 KR topics를 빌드타임에 주입한다(EN frontmatter 직접 수정 불필요 → 중복/드리프트 구조적 차단). resolveTopics 파서, filterAdrsByTopic(date desc) 인덱스 빌더, site-content ADR_TOPICS 6주제 카탈로그(기존 4+신규 security/product 2)로 확장한다. adrIds 하드코딩을 제거하고 frontmatter 집계로 전환한다. KR frontmatter 백필 15파일(영구 ADR 8개 frontmatter 신규삽입 + sprint/topic 7개 병합, multi-topic 지원: ADR-003/ADR-025=operations+security, sprint-95-programmers-dataset=data+product). check-adr-conversion F11/F12 fixture 추가.
D3. ADR 그래프 범례·필터 + WCAG AA 색상 (Palette)
filterAdjacency 순수 헬퍼 + adr-graph-view를 server→client 컴포넌트로 전환(필터 상태 activeKinds/showResolved/showUnresolved). 필터 UI는 kind 3토글(sprint/topic/permanent) + 엣지 2토글(resolved/unresolved), role=checkbox 접근성. 범례는 kind 3색 스와치 + 의미 설명. related-adr-graph에 KIND_COLORS로 노드 kind 색상을 적용한다. i18n ko+en 11키. 보강(e1e9d70): filterAdjacency unresolved 엣지 버그 수정(toOk = resolved ? has(to) : true) + WCAG AA 색상(sprint #0e7490 5.35:1 / topic #7c3aed 5.71:1 / permanent #2347e6 6.75:1, 모두 흰 텍스트 4.5:1+) + orphan graphNodeNormal 키 제거.
구현
구현 커밋 (5커밋, 51파일 +654/-99)
9eeeff0feat — 글 카테고리 7분류 교체 (KO+EN 동시)3dc780cchore — check-blog-crosscheck category enum 7분류 동기화 + SSOT 주석28156b6feat — Sprint 189 D2 ADR 주제 frontmatter 자동 분류 (KO+EN)1c297dafeat — ADR 관계 그래프 범례·필터 강화 (KO+EN)e1e9d70fix — Critic 발견 3건 수정 (그래프 필터 버그 + WCAG AA + orphan 키)
변경 주요 파일:
D1 (Herald):
blog/src/lib/posts.ts—VALID_CATEGORIES7분류 상수 +PostMeta.category타입 갱신 + fallbackblog/src/components/category-tabs.tsx— 동적 렌더(availableCategories 기반, 빈 탭 graceful skip)blog/src/components/post-list-with-filter.tsx— availableCategories useMemoblog/src/components/post-card.tsx— 배지 7분류blog/src/lib/i18n.ts— category 7키 ko+en 추가blog/content/posts/·posts-en/10편×2 — frontmatter category 재분류scripts/check-blog-crosscheck.mjs— VALID_CATEGORIES enum 7분류 동기화 + SSOT 주석
D2 (Architect):
blog/src/lib/adr-parser.ts—resolveTopics+AdrMeta.topicsblog/src/lib/adr-loader.ts— EN locale topics KR 주입(빌드타임)blog/src/lib/adr-index-builder.ts—filterAdrsByTopic(date desc)blog/src/lib/site-content.ts—ADR_TOPICS6주제(adrIds제거 → frontmatter 집계) + security/product 신규blog/src/lib/i18n.ts— ADR 주제 ko+en 4키blog/src/components/adr-topic-collections.tsx— frontmatter 집계 전환blog/content/adr/·adr-en/frontmatter 백필 15파일 (영구 ADR 8개 + sprint/topic 7개)scripts/check-adr-conversion.mjs— F11/F12 fixture 추가
D3 (Palette):
blog/src/lib/adr-graph.ts—filterAdjacency순수 헬퍼 + KIND_COLORSblog/src/components/adr-graph-view.tsx— server→client, 필터 상태(activeKinds/showResolved/showUnresolved), 필터 UI, 범례blog/src/components/related-adr-graph.tsx— KIND_COLORS 노드 kind 색상blog/src/lib/i18n.ts— 그래프 범례/필터 ko+en 11키
Critic 사이클
- D1 wave auto-critic —
codex review --base main0건. 카테고리 enum 타입 안전, graceful skip 구현, EN 한국어 잔류 없음. - D2 wave auto-critic —
codex review --base main0건. loader KR→EN topics 주입, filterAdrsByTopic 인덱스, fixture 추가 모두 이상 없음. - D3 wave auto-critic (1R) —
codex review --base main3건 발견:- P1:
filterAdjacencyunresolved 엣지의to노드 미포함 버그 (toOk 로직 오류) - P2: 색상 WCAG AA 미달 (원래 sprint/topic/permanent 색상)
- P3: i18n에 orphan 키(
graphNodeNormal) 잔재 - →
e1e9d70보강 커밋으로 전건 해소.
- P1:
- 최종 consolidated (
codex review --base main) — 0건. "Critical/High/Medium 0. Category tab filtering, topic frontmatter injection, and graph filter/legend are consistent and type-safe."
검증
브라우저 end-to-end (blog 빌드 후 정적 서버 + DOM)
- 카테고리 7분류 (KO/EN): 전체 + 6 탭(frontend 글 없음 → graceful skip으로 탭 미렌더) · 각 글 배지 7분류 정확 · i18n 잔재 0 ✓
- ADR 주제 6컬렉션 (KO
/adr· EN/en/adr): 운영/장애 4건·Agent 3건·CI 3건·Data 3건·신규 보안 3건·신규 제품 2건 · multi-topic ADR(ADR-003/ADR-025/sprint-95-programmers-dataset) 양쪽 컬렉션 동시 출현 · KR→EN topics 주입 동작(EN frontmatter 수정 없이 KR SSOT 반영) ✓ - 그래프 범례·필터 (
/adr·/en/adr): kind 필터(135→9 스프린트 한정)·엣지 필터·kind별 색상(sprint/topic/permanent)·범례 스와치+설명 · 현재 unresolved 엣지 0건(SVG 점선 0 확인) ✓ - 무회귀: 글/ADR 상세 기존 라우트 정상 · EN 한국어 잔류 0 ✓
로컬
tsc --noEmit0 errors ·npm run build전 라우트 정적 prerender.- 블로그/ADR 게이트 무회귀:
- i18n-residue (2.19%<8%)
- doc-refs (339 0 broken)
- adr-links KO/EN 0 broken
- adr-conversion (12/12, F11/F12 신규)
- index-count (8/1/127→128 본 ADR 추가 후)
- en-coverage (136/136→137/137 본 ADR 추가 후)
- blog-crosscheck (KR10/EN10 0 위반)
CI
- 구현 커밋 — Build Blog SSG·Coverage Gate·E2E Programmers all SUCCESS.
결과
- 구현: feat/sprint-189-blog-phase5 브랜치, 5커밋(
9eeeff0·3dc780c·28156b6·1c297da·e1e9d70), 51파일 +654/-99. - 5 Phase 개편 완성: Phase 1(Sprint 185 홈 랜딩+Engineering Editorial) → Phase 2(Sprint 186 ADR 큐레이션) → Phase 3(Sprint 187 글 상세 PDR) → Phase 4(Sprint 188 About+Footer) → Phase 5(이번, 탐색 UI 완결).
- ADR sprint-189(KR+EN) + README sprint ADR count 127→128·범위 62~189 (본 /stop 커밋).
신규 패턴
- frontmatter 주제 자동분류 = KR SSOT + loader EN 주입: KR frontmatter에
topics배열을 선언하고, loader가 EN locale 렌더 시 KR topics를 빌드타임 주입한다. EN frontmatter를 수동으로 복사·갱신하는 작업이 사라지고 드리프트가 발생 불가다. 다중 주제(multi-topic) ADR은 배열로 양쪽 컬렉션에 자동 출현한다(Sprint 186 큐레이션의adrIds하드코딩 제거). - 동적 카테고리 탭 = 글 존재 카테고리만 렌더:
availableCategoriesuseMemo로 실제 글이 있는 분류만 탭으로 만든다. 7분류로 확장해도 콘텐츠가 없는 카테고리(frontend)는 탭에 노출되지 않으며, 글이 추가되면 자동으로 탭이 나타난다(graceful skip으로 UX 부채 없음). - 게이트 enum SSOT 동기화 + SSOT 주석:
VALID_CATEGORIES같은 SSOT 상수를 변경할 때 검증 게이트(check-blog-crosscheck)의 enum도 동시 갱신하고, 소스에 "SSOT 주석"(// SSOT: posts.ts VALID_CATEGORIES와 동기화 유지)을 남겨 미래 담당자가 갱신 대상을 명시적으로 인식하게 한다.