리뷰 일자: 2026-01-20 대상:
templates/index.html+static/js/modules/전체
- templates/index.html
- main.js (진입점)
- AuthManager.js
- ContentGenerator.js
- UIManager.js
- UrlManager.js
- ProviderManager.js
- StyleManager.js
- ReportManager.js
- ModalManager.js
- StorageManager.js
- HistoryPanelManager.js
- UsagePanelManager.js
- ThemeManager.js
- MindmapManager.js
- PanelResizeManager.js
- KeyboardNavigationManager.js
- 종합 평가
- 규모: 약 2,500줄 이상의 대형 단일 파일
- 구조: HTML + 인라인 Tailwind 설정 + 1,000줄 이상의
<style>블록 - 기능: FOUC 방지 스크립트, WCAG AA 색상 대비, 다크/라이트 테마, Creative Studio 디자인 시스템
장점:
- WCAG AA 접근성 기준 충족 (색상 대비 4.5:1 이상)
- CSS 변수를 활용한 체계적인 테마 시스템
- 타이포그래피 스케일 일관성 (최소 14px 보장)
- 스켈레톤 로딩, 애니메이션 등 UX 고려
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 높음 | CSS가 HTML에 인라인으로 포함됨 | 유지보수 어려움, 캐싱 불가 |
| 높음 | 파일 크기가 너무 큼 (64K+ 토큰) | 초기 로딩 지연 |
| 중간 | Tailwind CDN 사용 | 프로덕션에서 성능 이슈, 버전 관리 어려움 |
| 중간 | 반복되는 CSS 패턴 | 코드 중복 |
| 낮음 | 일부 하드코딩된 색상값 | 테마 일관성 저하 가능 |
-
CSS 분리:
<style>블록을static/css/main.css로 추출/static/css/ ├── base.css # 변수, 리셋 ├── components.css # 카드, 버튼, 모달 등 ├── layouts.css # 그리드, 패널 └── animations.css # 애니메이션 -
Tailwind 빌드 도입: CDN 대신 PostCSS 빌드 파이프라인 구성
npx tailwindcss -i ./src/input.css -o ./static/css/output.css --minify
-
컴포넌트 템플릿화: Jinja2 매크로 또는 include로 반복 요소 분리
- 역할: 오케스트레이터 - 모든 모듈 초기화 및 연결
- 구조:
ContentAnalysis클래스 + 간단한 EventBus 패턴 - 라인 수: ~540줄
장점:
- 명확한 모듈 의존성 주입 패턴
- 초기화 순서가 잘 정리됨
- EventBus를 통한 느슨한 결합
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 중간 | EventBus가 main.js 내부에 인라인 정의 | 재사용 어려움 |
| 중간 | handleAiAnalyze, handleGenerateStyle이 main에 있음 |
관심사 분리 부족 |
| 낮음 | DOM 조작 코드가 일부 포함 | 모듈 경계 모호 |
- EventBus 분리:
static/js/core/EventBus.js가 이미 존재하므로 이것을 사용 - 기능 이동: AI 분석/스타일 생성 로직을
ContentGenerator로 이동 - 설정 객체 도입: 모듈 설정을 별도 config 객체로 관리
- 역할: Supabase 인증, OAuth PKCE 플로우
- 라인 수: ~540줄
- 패턴: SDK 기반 인증 + 서버 API 하이브리드
장점:
- PKCE OAuth 자동 처리
- 세션 자동 갱신
- 깔끔한 UI 상태 관리
- 하위 호환성 메서드 유지
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 높음 | _fetchJson에서 에러 객체 구조 불일치 |
디버깅 어려움 |
| 중간 | 모달 이벤트 중복 등록 방지 플래그(_modalEventsSetup)가 불안정 |
잠재적 메모리 누수 |
| 낮음 | 일부 하드코딩된 문자열 | 국제화 어려움 |
// 개선: 에러 처리 일관성
async _fetchJson(url, options = {}) {
try {
const response = await fetch(url, { ... });
const data = await response.json();
return {
ok: response.ok,
status: response.status,
data
};
} catch (error) {
return {
ok: false,
status: 0,
data: { error: error.message },
networkError: true
};
}
}- 역할: API 호출, 단일/배치 URL 처리
- 라인 수: ~210줄
- 구조: 단순하고 명확한 책임
장점:
- 백그라운드 처리로 UX 개선
- 인증 헤더 자동 첨부
- 에러 상태별 적절한 처리
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 중간 | processSingleUrl과 processUrlInBackground 중복 로직 |
유지보수성 저하 |
| 중간 | 재시도 로직 없음 | 일시적 네트워크 오류에 취약 |
| 낮음 | 배치 처리 시 동시 요청 제한 없음 | 서버 과부하 가능 |
- 공통 로직 추출: 요청 생성 부분을 private 메서드로 분리
- 재시도 로직 추가: 지수 백오프로 최대 3회 재시도
- 동시성 제한:
Promise.all에 동시 요청 수 제한 추가
- 역할: 알림, 로딩 상태, 유틸리티 함수
- 라인 수: ~250줄
장점:
- XSS 방지 (
escapeHtml,sanitizeUrl,sanitizeHtml) - 알림 자동 제거
- 일관된 로딩 상태 관리
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | sanitizeHtml이 기본적인 패턴만 처리 |
복잡한 XSS 우회 가능 |
| 낮음 | 알림 컨테이너가 없으면 무시 | 조용한 실패 |
// 더 강력한 sanitize를 위해 DOMPurify 사용 권장
import DOMPurify from 'dompurify';
sanitizeHtml(html) {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target', 'rel']
});
}- 역할: URL 추가/삭제, 드래그앤드롭
- 라인 수: ~140줄
장점:
- 최대 URL 수 제한 (10개)
- 중복 URL 방지
- 드래그앤드롭으로 순서 변경
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | URL 유효성 검사가 정규식만 의존 | 비정상 URL 허용 가능 |
| 낮음 | 드래그 상태가 인스턴스 변수로 관리 | 다중 드래그 시 문제 가능 |
// URL 유효성 추가 검증
addUrl(url) {
url = url.trim();
try {
const urlObj = new URL(url);
if (!['http:', 'https:'].includes(urlObj.protocol)) return false;
} catch {
return false;
}
// ... 기존 로직
}- 역할: AI 프로바이더/모델 선택 관리
- 라인 수: ~190줄
장점:
- 서버에서 사용 가능한 프로바이더만 로드
- 설정 자동 복원
- 깔끔한 select 동기화
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | 프로바이더 로드 실패 시 재시도 없음 | 초기화 실패 가능 |
| 낮음 | 기본 프로바이더가 하드코딩 ('gemini') | 설정 의존성 |
- 프로바이더 목록을 config에서 관리
- 로드 실패 시 재시도 + 오프라인 캐시
- 역할: 스타일 선택, 커스텀 스타일, 모디파이어
- 라인 수: ~250줄
장점:
- 키보드 네비게이션 지원
- 터치 이벤트 지원
- 언어 칩과 select 동기화
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 중간 | styleLabels가 영어로 하드코딩 |
UI 일관성 저하 |
| 낮음 | 커스텀 스타일 편집 버튼이 hover에만 표시 | 모바일에서 접근 어려움 |
// 스타일 라벨을 서버/config에서 관리
async loadStyleLabels() {
const response = await fetch('/api/style-labels');
this.styleLabels = await response.json();
}- 역할: 리포트 카드 생성, 표시, 히스토리
- 라인 수: ~630줄 (가장 큰 모듈)
장점:
- 새로운 Result Card 시스템 (제목/본문/메타 분리)
- 스켈레톤 로딩 UI
- 복사 버튼 피드백
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 높음 | HTML 템플릿이 문자열로 하드코딩 | 유지보수 매우 어려움 |
| 중간 | 파일이 너무 큼 (600줄+) | 가독성 저하 |
| 중간 | _build*Html 메서드들이 길고 복잡 |
테스트 어려움 |
-
템플릿 분리: HTML 템플릿을 별도 파일로 분리
/templates/components/ ├── pending-card.html ├── result-card.html └── error-card.html -
클래스 분리:
// ReportCardBuilder.js - 카드 HTML 생성 // ReportCardEvents.js - 이벤트 핸들링 // ReportManager.js - 오케스트레이션
- 역할: Settings, Onboarding, Custom Style 모달 관리
- 라인 수: ~450줄
장점:
- ESC 키로 모달 닫기
- 모달 우선순위 관리
- 이벤트 리스너 중복 등록 방지 (cloneNode 패턴)
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 중간 | 각 모달별 메서드가 유사한 패턴 반복 | 코드 중복 |
| 낮음 | 프롬프트 길이 제한이 클라이언트에서만 검증 | 서버 검증 필요 |
// 제네릭 모달 핸들러
class GenericModal {
constructor(id, options = {}) {
this.modal = document.getElementById(id);
this.closeBtn = this.modal.querySelector('[data-close]');
// ...
}
show() { ... }
hide() { ... }
}- 역할: localStorage 관리
- 라인 수: ~170줄
장점:
- 버전 관리 키 사용 (
cad_*_v1) - 최대 항목 수 제한
- 에러 처리 포함
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 중간 | localStorage 용량 초과 처리 없음 | 데이터 손실 가능 |
| 낮음 | 히스토리 항목이 커지면 성능 저하 | 느린 로드/저장 |
saveHistory(history) {
try {
const json = JSON.stringify(history);
if (json.length > 4 * 1024 * 1024) { // 4MB 제한
// 오래된 항목부터 삭제
history = history.slice(0, Math.floor(history.length / 2));
}
localStorage.setItem(this.keys.history, JSON.stringify(history));
} catch (e) {
if (e.name === 'QuotaExceededError') {
this.clearOldHistory();
}
}
}- 역할: 히스토리 패널 UI
- 라인 수: ~170줄
장점:
- 이벤트 위임 패턴 사용
- 시간 포맷팅 (n분 전, n시간 전)
- XSS 방지 escapeHtml
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | 큰 히스토리에서 전체 재렌더링 | 성능 저하 |
| 낮음 | 가상 스크롤 미적용 | 많은 항목 시 느림 |
- 가상 스크롤 라이브러리 도입 (예: virtual-scroll)
- 또는 페이지네이션 적용
- 역할: 사용량 패널 UI, 주간 차트
- 라인 수: ~150줄
장점:
- 주간 사용량 시각화
- 스타일별 통계
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | 차트가 HTML 문자열로 생성 | 복잡한 차트 확장 어려움 |
| 낮음 | API 실패 시 기본값만 표시 | 사용자 혼란 |
- 경량 차트 라이브러리 고려 (Chart.js 또는 ApexCharts)
- 역할: 다크/라이트/시스템 테마 관리
- 라인 수: ~230줄
장점:
- 시스템 설정 감지 (
prefers-color-scheme) - 부드러운 전환 애니메이션
- 3단계 토글 (light → dark → system)
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | destroy() 메서드에서 toggleTheme 언바인딩 오류 |
메모리 누수 가능 |
// 바인딩된 함수 저장
constructor() {
this._boundToggleTheme = this.toggleTheme.bind(this);
}
destroy() {
this.mediaQuery.removeEventListener('change', this.handleSystemChange);
const btn = document.getElementById('theme-toggle-btn');
if (btn) {
btn.removeEventListener('click', this._boundToggleTheme);
}
}- 역할: 마인드맵 생성 및 시각화
- 라인 수: ~175줄
- 의존성: Markmap 라이브러리
장점:
- 캐시된 마인드맵 재사용
- 폴백 렌더링 (Markmap 실패 시)
- 줌 컨트롤
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 높음 | getApiKey() 메서드 호출 - StorageManager에 없음 |
런타임 에러 |
| 중간 | 에러 시 모달이 닫히면서 에러 메시지가 안 보일 수 있음 | UX 저하 |
// API 키 방식 수정 - 서버 인증 사용
async generateMindmap(content, title) {
const response = await fetch('/api/mindmap', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this._getAuthHeaders() // AuthManager에서 가져오기
},
body: JSON.stringify({ content, model })
});
}- 역할: 패널 리사이즈 기능
- 라인 수: ~330줄
장점:
- 키보드 접근성 지원 (화살표 키, Home/End)
- 터치 이벤트 지원
- 크기 자동 저장/복원
- 반응형 (xl 이상에서만 활성화)
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | ResizeObserver 무한 루프 가능성 | 성능 이슈 |
setupResizeObserver() {
// 디바운스 추가
let timeout;
const observer = new ResizeObserver(() => {
clearTimeout(timeout);
timeout = setTimeout(() => this.handleWindowResize(), 100);
});
}- 역할: 키보드 접근성 (US-013)
- 라인 수: ~530줄
장점:
- 포커스 모드 감지 (keyboard vs mouse)
- 모달 포커스 트랩
- Skip Link
- 그리드/칩/사이드바 화살표 네비게이션
| 우선순위 | 문제 | 영향 |
|---|---|---|
| 낮음 | init()이 constructor에서 자동 호출 |
테스트 어려움 |
| 낮음 | 동적으로 추가되는 요소는 접근성 속성 미적용 | 일부 요소 접근성 저하 |
- MutationObserver로 동적 요소 감지
- 접근성 속성 자동 적용
| 항목 | 점수 | 평가 |
|---|---|---|
| 모듈화 | ★★★★☆ | 잘 분리되어 있으나 일부 책임이 겹침 |
| 확장성 | ★★★☆☆ | 새 기능 추가는 쉬우나 기존 코드 수정 필요 |
| 유지보수성 | ★★★☆☆ | HTML 템플릿 하드코딩이 주요 문제 |
| 접근성 | ★★★★☆ | WCAG AA 준수, 키보드 네비게이션 우수 |
| 보안 | ★★★★☆ | XSS 방지, API 키 서버 관리 |
| 성능 | ★★★☆☆ | 대형 파일, CDN 의존성 개선 필요 |
- MindmapManager의
getApiKey()버그 수정 - index.html CSS 분리 시작
- ReportManager 리팩토링 (템플릿 분리)
- Tailwind 빌드 파이프라인 도입
- EventBus 통합
- DOMPurify 도입
- 가상 스크롤 적용
- API 요청 재시도 로직
- 다국어 지원 준비
총 파일 수: 18개 (HTML 1 + JS 17)
총 라인 수: 약 5,500줄 (CSS 제외)
평균 파일 크기: ~300줄
가장 큰 파일:
1. ReportManager.js - 634줄 ⚠️ 분리 권장
2. KeyboardNavigationManager.js - 529줄
3. main.js - 543줄
4. AuthManager.js - 541줄
이 리뷰 문서는 코드 품질 향상을 위한 참고용입니다.