bibimbap/.atp/work-session/20260630-113258/research/frontend-analysis.md

16 KiB

phase agent agent_version generated_at concerns source_confidence workers_spawned
research research-advisor 2 2026-06-30T02:41:09Z
high 5

프론트엔드 중복/이슈 분석

주제

JSP 기반 프론트엔드 코드에서 컴포넌트 중복 구현, 유지보수·확장성 이슈 조사


확정 중복 패턴 (이미 파악된 사실)

JS 함수 4중 중복 — admin 4개 파일

함수 파일 영향도
notify(message) admin-console, admin-jam-list, admin-post-categories, admin-unity-feeds 각각 선언 Medium
post(url, params) 동일 4개 파일. 구현이 약간씩 다름 (params 처리, _csrf 추가 여부) High
handleResult(res), handleError() 동일 4개 파일 동일 패턴 Medium

신뢰도: 확인됨 (입력 사실 보존)

BibimbapModal.alert 체크 인라인 반복

  • window.BibimbapModal && typeof window.BibimbapModal.alert === 'function' 패턴이 15개 이상 파일에 인라인 반복
  • 신뢰도: 확인됨 (입력 사실 보존, 탐색 E에서 구체 위치 확인)

CSRF 토큰 추출 방식 불일치

  • admin-console/jam-scoring/jam-detail: request.getAttribute("csrfToken") rawCsrf 패턴
  • admin-post-categories/admin-unity-feeds: (String) request.getAttribute("csrfToken") 캐스팅
  • signup/login: CsrfTokens.getOrCreate(request.getSession()) 직접 호출
  • JS에서 일부는 <meta name="_csrf"> 읽기, 일부는 <%= csrfTokenJs %> 직접 출력
  • 신뢰도: 확인됨 (입력 사실 보존)

CSS 중복 (탐색 A)

규모 요약

  • 전체 27개 JSP 파일 중 26개에 인라인 <style> 블록 존재 (theme-init.jsp 제외)
  • 누적 인라인 CSS: 5,969줄
  • 범위: footer.jsp 76줄 ~ game-detail.jsp 1,173줄
  • 신뢰도: 확인됨

중복 패턴 1위 — html {} 라이트 테마 CSS 변수 선언 (23개 파일)

핵심 8개 색상 토큰(--surface: #faf8f5, --card-bg: #fff, --text: #1a1a1a, --text-muted, --accent: #e8a54b, --border, --shadow, --accent-soft)이 23개 파일에 동일 값으로 복사됨.

  • 대표 위치: admin-console.jsp:38, jam-list.jsp:21, login.jsp:12
  • 신뢰도: 확인됨

중복 패턴 2위 — html[data-theme="dark"] {} 다크 테마 CSS 변수 선언 (23개 파일)

다크 팔레트(#121212, #1e1e1e, #ece8e1, #a39e96, rgba(255,255,255,0.1))가 23개 파일에 동일 반복.

  • 대표 위치: admin-console.jsp:51, posts-list.jsp:41, recruit-list.jsp:34
  • 신뢰도: 확인됨

중복 패턴 3위 — body {} 기본 리셋 + 폰트 + 배경 (23개 파일)

margin:0; min-height:100vh; font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans KR", sans-serif; background:var(--surface); color:var(--text); 블록이 23개 파일에 동일 반복.

  • 대표 위치: admin-console.jsp:62, jam-list.jsp:41, terms.jsp:29
  • 신뢰도: 확인됨

중복 패턴 4위 — 페이지 컨테이너 safe-area padding 패턴 (25개 파일)

padding: 1.5rem max(1rem, env(safe-area-inset-left)) 3rem max(1rem, env(safe-area-inset-right)); 가 거의 모든 페이지에 반복. max-width 값만 48/56/64/72/78rem으로 파일별 상이.

  • 대표 위치: admin-console.jsp:70-72, posts-list.jsp:58-60, recruit-list.jsp:52-54
  • 신뢰도: 확인됨

중복 패턴 5위 — .admin-btn / .admin-btn--primary / .admin-btn--danger (4개 admin 파일)

버튼 컴포넌트 CSS 20줄 이상이 4개 admin 파일에 동일 복사.

  • 위치: admin-console.jsp:171-194, admin-jam-list.jsp:167-190, admin-post-categories.jsp:100, admin-unity-feeds.jsp:108
  • 신뢰도: 확인됨

중복 패턴 6위 — .detail-button / .detail-actions / .detail-section (2개 파일)

  • jam-detail.jsp:128, recruit-detail.jsp:116 — 거의 동일 (color 값 1곳만 다름: #1a1a1a vs var(--button-text))
  • 신뢰도: 확인됨

CSS Grid 리스트 래퍼 패턴 (posts·recruit 동일)

  • posts-list.jsp:119-123.posts-grid { display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:1rem; }
  • recruit-list.jsp:143-147.recruit-grid { ... } 동일 구조
  • 신뢰도: 확인됨

리스트 페이지 패턴 (탐색 B)

카드 HTML 구조 비교

항목 posts-list.jsp recruit-list.jsp jam-list.jsp
래퍼 클래스 .posts-grid (section) .recruit-grid (section) .jam-grid (div)
Grid 열 repeat(3, minmax(0,1fr)) repeat(3, minmax(0,1fr)) repeat(auto-fill, minmax(16rem,1fr))
카드 태그 <a class="post-card"> <a class="recruit-card"> <a class="jam-card">
이미지 있음 (16/9 aspect-ratio) 없음 없음
데이터 속성 없음 data-role, data-type, data-search 없음

신뢰도: 확인됨

검색/필터 방식 비교

  • posts-list.jsp — 검색 UI 없음. 카테고리 탭(<nav class="posts-tabs">)은 <a> 링크 방식으로 서버 재요청(?categoryId=). JS 없음.
  • recruit-list.jsp<input type="search" id="recruit-search"> (라인 261) + 역할·참여형태 <button class="filter-chip"> (aria-pressed 토글). applyFilters() 함수가 card.hidden으로 클라이언트 필터 (라인 328-340). 검색 텍스트는 서버가 data-search 속성에 pre-encode.
  • jam-list.jsp — 검색/필터 모두 없음.
  • 신뢰도: 확인됨

페이지네이션 방식 비교

  • posts-list.jsp — 커서 기반 "더보기" 링크. 서버 Java가 cursorCreatedAt, cursorId 파라미터를 URL로 조립 (라인 263-270). JS 없음.
  • recruit-list.jsp — 페이지네이션 없음. 전체 목록 한 번에 렌더.
  • jam-list.jsp — 커서 기반 "더보기" 링크. 서버 Java가 cursor 파라미터 URL 조립 (라인 177-180). JS 없음.
  • 신뢰도: 확인됨

공통 중복 블록

  • CSS 변수 + body 스타일 블록: 세 파일 모두 동일 (상위 CSS 중복 패턴과 동일)
  • 히어로 섹션 flex 레이아웃: posts·recruit 동일 (align-items:flex-end; justify-content:space-between; gap:1rem)
  • 쓰기 버튼 스타일: posts·recruit 동일 (min-height:2.75rem; border-radius:10px; background:var(--accent))
  • 카드 hover 효과: posts·recruit 동일, jam은 transform 없이 border-color만
  • empty-state 박스: 세 파일 유사 (border-style만 dashed vs solid 차이)
  • 반응형 미디어 쿼리(3열→2열→1열): posts·recruit 동일 (@media 900px/640px)
  • request.getContextPath() + 목록 attribute 캐스팅 패턴: 세 파일 공통
  • <jsp:include page="/WEB-INF/views/header.jsp"/> / footer.jsp: 세 파일 동일
  • 신뢰도: 확인됨

폼 페이지 패턴 (탐색 C)

공통 골격 (거의 복사-붙여넣기 수준)

두 파일은 fetch 기반 비동기 폼 제출, checkValidity() 검증, BibimbapModal.alert 성공/실패 처리, BibimbapCsrf CSRF 헤더 주입이라는 동일한 골격을 공유.

항목 posts-form.jsp recruit-form.jsp
body 구성 new URLSearchParams(new FormData(form)) (라인 232) 동일 (라인 383)
Content-Type application/x-www-form-urlencoded;charset=UTF-8 (라인 225) 동일 (라인 387)
클라이언트 검증 form.checkValidity() + form.reportValidity() (라인 219-222) 동일 (라인 378-381)
res.ok 판정 if (!res.ok) { var error = new Error(...); error.status = res.status; throw error; } (라인 241-246) 동일 (라인 400-404)
성공 리다이렉트 window.location.href = ctx + (data.location || '/posts') (라인 250) window.location.href = '<%= ctx %>' + (data.location || '/recruit') (라인 409)

신뢰도: 확인됨

차별화 부분 (다른 점)

  1. CSRF 처리 방식 불일치 (High)

    • posts-form.jsp:169<input type="hidden" name="_csrf" value="<%= csrfTokenHtml %>"> hidden input 존재 + JS 변수 이중 포함 (posts-form.jsp:211, 229)
    • recruit-form.jsp:386-394 — hidden CSRF input 없음. BibimbapCsrf 미존재 시 fallback 헤더에 CSRF 토큰이 전혀 포함되지 않는 보안 갭
    • 신뢰도: 확인됨
  2. 성공 시 모달 없을 때 분기 차이

    • posts-form.jsp:260-261} else { go(); } (alert 없음)
    • recruit-form.jsp:419-421} else { alert('팀원 모집글이 등록되었습니다.'); go(); }
    • 신뢰도: 확인됨
  3. 401 리다이렉트 처리 패턴 불일치

    • posts-form.jsp:264-268redirectLogin 함수 분리, onConfirm에서 호출
    • recruit-form.jsp:430-431, 437-439 — 모달 onConfirm과 else 분기 양쪽에 각각 인라인 중복 작성
    • 신뢰도: 확인됨
  4. 수정 모드: posts-form.jsp:9-10, 215mode, postId 로 등록/수정 URL 분기. recruit-form.jsp는 수정 모드 분기 없음.

  5. 실시간 미리보기: recruit-form.jsp:340-373pairs 배열 + render() + input/change 이벤트로 우측 preview-card 갱신. posts-form에는 없음.

  6. 제출 버튼 disabled 처리: 두 파일 모두 없음. fetch 중 비활성화 처리 미구현.


날짜 함수 (탐색 D)

발견 요약

  • 날짜/시간 포맷 JavaScript 함수가 선언된 파일: game-detail.jsp 단 하나
  • 나머지 6개 파일(posts-detail, recruit-detail, jam-detail, posts-list, jam-list, recruit-list)에는 날짜 포맷 JS 함수가 전혀 없음
  • 신뢰도: 확인됨

game-detail.jsp 날짜 함수 (라인 1516-1551)

함수 위치 구현 방식
fmtAbsolute(iso) game-detail.jsp:1516-1519 new Date(iso).toLocaleString('ko-KR')
fmtRelative(iso) game-detail.jsp:1521-1536 경과 시간 직접 산술 계산 (60/3600/86400초 분기)
buildTimeEl(iso, edited) game-detail.jsp:1538-1551 두 함수 합성, <time> 요소 + "(수정됨)" 뱃지 반환
  • Intl.RelativeTimeFormat / Intl.DateTimeFormat 직접 사용: 조사 범위 7개 파일 전체에서 0 hit
  • 신뢰도: 확인됨

사용 위치

  • game-detail.jsp:1878buildTimeEl 사용 (댓글 항목)
  • game-detail.jsp:2183-2186fmtAbsolute/fmtRelative 직접 사용 (리뷰 항목)
  • 신뢰도: 확인됨

서버사이드 날짜 포매팅

  • 조사 범위 내 Java Date 포매팅으로 화면 출력하는 경우: 0건
  • posts-list.jsp:6-7,19OffsetDateTime import하지만 커서 URL 생성에만 사용 (화면 출력 아님)
  • jam-detail.jsp:442OffsetDateTime.now().isAfter(...) 서버사이드 분기 조건 (화면 포맷 출력 아님)
  • 신뢰도: 확인됨

구조적 위험

fmtAbsolute/fmtRelative/buildTimeEl이 game-detail.jsp 인라인 스크립트에만 존재 → 다른 detail 페이지가 동일 기능이 필요할 때 복사 중복 발생 구조.


모달 패턴 (탐색 E)

BibimbapModal API (modal.jsp:174-238)

메서드 시그니처 설명
alert(options) { title, message, confirmText, onConfirm } 취소 버튼 없음, 확인 버튼만
confirm(options) { title, message, confirmText, cancelText, onConfirm, onCancel } 확인+취소 양쪽 콜백
prompt(options) { title, message, label, value, placeholder, maxLength, confirmText, cancelText, onConfirm(inputValue), onCancel } 텍스트 입력 필드 포함
  • 구현 방식: 단일 <div id="site-modal"> 재사용, 콜백 기반 (Promise 아님), Enter=확인/Escape=취소 키보드 처리, 포커스 복귀
  • 신뢰도: 확인됨

모달 사용 패턴 분류 (12개 JSP, modal.jsp 제외)

패턴 A — openModal() 헬퍼 래퍼 (4개 파일, High 중복) login.jsp, signup.jsp, game-register.jsp, profile.jsp가 동일 시그니처의 openModal(title, message, confirmText, onConfirm) 로컬 함수를 각자 정의.

  • 내부 BibimbapModal 체크 후 폴백 alert(message) + 동기 onConfirm() 호출
  • 위치: login.jsp:256, signup.jsp:269, game-register.jsp:490, profile.jsp:539
  • 신뢰도: 확인됨

패턴 B — notify() 헬퍼 래퍼 (4개 admin 파일, High 중복) admin-console.jsp, admin-jam-list.jsp, admin-post-categories.jsp, admin-unity-feeds.jspnotify(message) 로컬 함수를 각자 정의 (alert 전용, title은 하드코딩된 페이지명).

  • 위치: admin-jam-list.jsp:389, admin-console.jsp:401, admin-post-categories.jsp:251, admin-unity-feeds.jsp:305
  • 신뢰도: 확인됨

패턴 C — 인라인 직접 체크 (3개 파일) recruit-form.jsp, posts-form.jsp, game-detail.jsp가 호출 지점마다 인라인으로 체크.

  • recruit-form.jsp:411, 424, posts-form.jsp:252, 269, game-detail.jsp:1425, 1439, 1474, 1482, 1553, 1558
  • 신뢰도: 확인됨

특이 케이스 — game-detail.jsp 혼용 game-detail.jsp:1553-1561notifyError(), confirmAction() 헬퍼를 별도 정의하면서, 동일 파일 1425-1449에 인라인 체크도 병존. 단일 파일 내 두 패턴 혼재.

  • 신뢰도: 확인됨

window.confirm/alert 직접 사용 (BibimbapModal 없이)

종류 위치 비고
window.confirm() 직접 호출 (체크 없음) admin-post-categories.jsp:323 삭제 확인 대화상자
window.confirm() 직접 호출 (체크 없음) admin-unity-feeds.jsp:401 삭제 확인 대화상자
window.alert() 폴백 (else 브랜치) 전 파일의 BibimbapModal 없을 때 else 브랜치 정상 폴백
window.prompt() 폴백 profile.jsp:686 BibimbapModal.prompt 실패 시
  • admin-post-categories.jspadmin-unity-feeds.jsp는 알림에는 notify() 헬퍼(BibimbapModal 체크)를 쓰면서 삭제 확인에는 window.confirm()을 직접 사용하여 동일 파일 내 불일치 존재
  • 신뢰도: 확인됨

종합 판단

상위 패턴

  1. CSS 인라인 토큰 중복이 가장 심각: 라이트/다크 테마 CSS 변수 블록과 body 기본 스타일이 23개 파일에 동일하게 복사됨. 디자인 토큰 변경 시 23개 파일을 모두 수정해야 하는 유지보수 고비용 구조. theme-init.jsp가 존재하나 CSS 변수는 여기에 없고 JS 테마 초기화만 담당하는 것으로 보임.

  2. JS 유틸리티 함수 인라인 중복 이중 구조: notify() (admin 4개), openModal() (일반 4개), post(url, params) (admin 4개) 등이 페이지마다 별도 선언됨. 특히 post() 함수는 파일마다 구현이 미묘하게 달라 동작 불일치 위험 내재.

  3. BibimbapModal 체크 패턴 3가지 혼존: 헬퍼 래퍼 A, 헬퍼 래퍼 B, 인라인 직접 체크 C가 서로 다른 파일에 혼재. 일관성 없고 신규 페이지 작성 시 어떤 패턴을 따라야 할지 기준 없음.

  4. 폼 제출 골격 복사: posts-form과 recruit-form이 fetch 제출 코드를 거의 복사 수준으로 공유하면서 CSRF 처리 방식에 불일치 존재 (recruit-form의 BibimbapCsrf 미존재 시 CSRF 토큰 누락 위험).

  5. 날짜 포맷 함수 편재: game-detail.jsp에만 날짜 함수가 존재하며 다른 detail 페이지는 날짜 렌더링 자체가 없거나 JS 미사용. 추후 다른 페이지에서 날짜 포맷이 필요해지면 복사 중복 발생 구조.

충돌/갭

  • CSS 변수 선언이 파일마다 포함하는 변수 수가 8~14개로 약간씩 달라, 특정 파일에서 일부 변수가 미선언된 상태일 수 있음.
  • admin-post-categories.jspadmin-unity-feeds.jsp는 알림에는 BibimbapModal 체크를 쓰면서 삭제 확인은 window.confirm() 직접 사용 — 같은 파일 내 불일치.
  • jam-detail.jsp:128recruit-detail.jsp:116.detail-button--primary color 값 불일치 (#1a1a1a vs var(--button-text)) — 디자인 일관성 갭.

미발견 / 조사 불충분 항목

  • theme-init.jsp 내부 구현 미탐색 — JS 테마 초기화 방식과 CSS 변수 일원화 가능성 미확인
  • header.jsp, footer.jsp에서 이미 CSS 변수를 선언하는지 여부 미탐색 — 이중 선언 여부 불명
  • modal.jsp가 실제로 어느 레이아웃 include 경로를 통해 로드되는지 미탐색 — BibimbapModal 미존재 상황의 실제 발생 가능성 미확인
  • game-detail.jsp 외 admin-* 파일들의 날짜 렌더링 패턴 미탐색
  • BibimbapCsrf 실제 구현체(별도 JS 파일) 미탐색 — headers() 메서드 폴백 동작 미확인
  • posts-form.jsp의 post() 함수와 admin 4개 파일의 post() 함수가 실제로 다른 구현인지 정밀 비교 미수행