docs(analysis): JSP 프론트엔드 컴포넌트 중복·유지보수 검토
- CSS 디자인 토큰 23개 파일 중복, JS 유틸 8중복, recruit-form CSRF 폴백 갭, 날짜 함수 편재 발견 - 권고: global.css 중앙화(P0), bibimbap-utils.js(P1), recruit-form CSRF 수정(P0-보안), bibimbap-date.js(P2) - work-session 아티팩트: research + design recommendations 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3FeMrbtxfTScjrwUukyHD
This commit is contained in:
parent
327fe1df25
commit
5e0f76b7c1
|
|
@ -0,0 +1,542 @@
|
||||||
|
---
|
||||||
|
phase: design
|
||||||
|
agent: design-advisor
|
||||||
|
agent_version: 1
|
||||||
|
generated_at: 2026-06-30T03:15:00Z
|
||||||
|
concerns:
|
||||||
|
- "admin-console.jsp, admin-jam-list.jsp JS 내부의 post() 함수 구현 세부가 admin-post-categories / admin-unity-feeds와 정확히 동일한지 코드 레벨 비교 필요. 통합 시 파라미터 처리 분기가 예상치 않게 동작 변경될 수 있음. 구현 advisor가 통합 전 3-way diff 수행 권고."
|
||||||
|
- "header.jsp 내 .site-header CSS 변수 블록(--header-bg 등)은 global.css 의 토큰 범위와 겹치지 않는 컴포넌트 전용 변수이므로 중앙화 대상에서 제외. 구현 시 헷갈리지 않도록 명칭 분리 유지 확인 요망."
|
||||||
|
concerns_checked: true
|
||||||
|
references:
|
||||||
|
requirements: "(인라인 입력 — 이 문서의 ## 맥락 섹션)"
|
||||||
|
research: ".atp/work-session/20260630-113258/research/frontend-analysis.md"
|
||||||
|
adrs: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# 프론트엔드 컴포넌트 개선 권고안
|
||||||
|
|
||||||
|
## 목표 / 비목표
|
||||||
|
|
||||||
|
### 목표
|
||||||
|
- FR-1: CSS 디자인 토큰 중복 제거 → 23개 파일 대신 단일 파일 변경으로 전역 반영
|
||||||
|
- FR-2: JS 공용 유틸리티(notify/openModal/post/handleResult/handleError) 중복 선언 제거
|
||||||
|
- FR-3: BibimbapModal 호출 패턴을 단일 방식으로 표준화
|
||||||
|
- FR-4: recruit-form의 CSRF 토큰 폴백 갭 수정 (보안)
|
||||||
|
- FR-5: 날짜 포맷 함수를 공용 파일로 추출하여 복사 중복 방지
|
||||||
|
|
||||||
|
### 비목표
|
||||||
|
- Webpack/Vite 등 빌드 도구 도입 (별도 의사결정 사항)
|
||||||
|
- CSS-in-JS, PostCSS 파이프라인 도입
|
||||||
|
- JSP 템플릿 엔진 교체 또는 React/Vue 마이그레이션
|
||||||
|
- 기존 HTML/JSP 구조 및 클래스명 리팩터링 (CSS 토큰만 외부화)
|
||||||
|
- header.jsp·footer.jsp·modal.jsp 기존 구현 변경
|
||||||
|
- 서버사이드 날짜 포매팅 도입
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 요약 (우선순위 매트릭스)
|
||||||
|
|
||||||
|
| 이슈 | 영향도 | 구현 비용 | 우선순위 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| CSS 토큰 23개 파일 중복 | 높음 (유지보수 23배 비용) | 낮음 (파일 추가 + include 삽입) | P0 |
|
||||||
|
| 폼 제출 CSRF 갭 (recruit-form) | 높음 (보안) | 낮음 (10줄 이내 수정) | P0 — 보안 |
|
||||||
|
| JS 공용 유틸리티 분리 | 중간 (동작 불일치 방지) | 중간 (파일 작성 + 기존 인라인 제거) | P1 |
|
||||||
|
| 모달 패턴 표준화 | 중간 (신규 페이지 기준 제공) | 중간 (패턴 A/B 대체 + admin 2곳 수정) | P1 |
|
||||||
|
| 날짜 포맷 유틸리티 모듈 | 낮음 (예방적 분리) | 낮음 (파일 추출) | P2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 권고 1 — CSS 토큰 중앙화 (P0)
|
||||||
|
|
||||||
|
### 접근
|
||||||
|
|
||||||
|
`src/main/webapp/css/global.css` 정적 파일을 신규 생성한다. 이 파일에 라이트/다크 테마 CSS 변수 블록, `body` 기본 스타일, `safe-area` padding 믹스인 클래스, `.admin-btn` 컴포넌트를 선언한다.
|
||||||
|
|
||||||
|
각 JSP의 `<head>` 최상단에 `<link rel="stylesheet" href="${pageContext.request.contextPath}/css/global.css">` 를 삽입한다. 이 link는 `theme-init.jsp` `<jsp:include>` 직후에 위치시켜 FOUC(Flash of Unstyled Content)를 방지한다.
|
||||||
|
|
||||||
|
Spring Boot WAR에서 `src/main/webapp/` 하위 정적 파일은 Servlet 컨테이너(Tomcat)가 직접 서빙한다. `UploadResourceConfig`의 `addResourceHandlers`는 `/profile/**` 전용이므로 `/css/**` 는 별도 등록 없이 Tomcat 기본 DefaultServlet이 처리한다. `src/main/resources/static/` 경로는 필요하지 않으며 신규 생성도 불필요하다.
|
||||||
|
|
||||||
|
### 파일 경로
|
||||||
|
|
||||||
|
| 역할 | 경로 |
|
||||||
|
|---|---|
|
||||||
|
| 신규 — 전역 CSS | `src/main/webapp/css/global.css` |
|
||||||
|
| 수정 대상 — 인라인 토큰 제거 | `src/main/webapp/WEB-INF/views/*.jsp` (23개) |
|
||||||
|
|
||||||
|
### global.css 선언 내용 (구체 명세)
|
||||||
|
|
||||||
|
```
|
||||||
|
/* 1. 라이트 테마 토큰 */
|
||||||
|
html {
|
||||||
|
color-scheme: light;
|
||||||
|
--surface: #faf8f5;
|
||||||
|
--card-bg: #fff;
|
||||||
|
--text: #1a1a1a;
|
||||||
|
--text-muted: #5c5c5c;
|
||||||
|
--accent: #e8a54b;
|
||||||
|
--accent-soft: rgba(232, 165, 75, 0.16);
|
||||||
|
--border: rgba(0, 0, 0, 0.08);
|
||||||
|
--shadow: rgba(0, 0, 0, 0.06);
|
||||||
|
--field-bg: #fff;
|
||||||
|
--button-text: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 다크 테마 토큰 */
|
||||||
|
html[data-theme="dark"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--surface: #121212;
|
||||||
|
--card-bg: #1e1e1e;
|
||||||
|
--text: #ece8e1;
|
||||||
|
--text-muted: #a39e96;
|
||||||
|
--border: rgba(255, 255, 255, 0.1);
|
||||||
|
--shadow: rgba(0, 0, 0, 0.35);
|
||||||
|
--field-bg: #181818;
|
||||||
|
--button-text: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. body 기본 스타일 */
|
||||||
|
body {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. admin-btn 컴포넌트 (admin 4개 파일 중복 제거) */
|
||||||
|
.admin-btn {
|
||||||
|
min-height: 2.25rem;
|
||||||
|
padding: 0 0.85rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.admin-btn:hover { border-color: rgba(232, 165, 75, 0.45); }
|
||||||
|
.admin-btn--primary { border-color: transparent; background: var(--accent); color: var(--button-text); }
|
||||||
|
.admin-btn--danger { border-color: rgba(200, 60, 60, 0.45); color: #c83c3c; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**포함하지 않는 것**: 각 JSP 고유 레이아웃 클래스(`.admin-page`, `.form-page`, `.auth-main` 등), `max-width` 값(파일마다 48/72/78rem으로 다름), 페이지 전용 컴포넌트 CSS. 이들은 계속 인라인으로 유지한다.
|
||||||
|
|
||||||
|
**`--danger` 토큰**: `admin-unity-feeds.jsp`에만 있는 `--danger: #c83c3c / #ef7878` 변수는 global.css에 추가하지 않는다. 해당 파일이 유일한 사용처이므로 인라인 유지가 맞다.
|
||||||
|
|
||||||
|
**`--accent-soft` 미선언 파일 처리**: `login.jsp`와 `profile.jsp`는 `--accent-soft`를 선언하지 않는다. global.css에 전역 선언 후 이 두 파일의 인라인 블록에서 해당 변수가 없어도 문제 없다 (사용 위치가 없거나 다른 변수로 대체됨). 구현 시 각 파일의 실제 사용처를 확인하고 미사용이면 그냥 삭제, 사용이면 global.css 선언이 적용되므로 정상.
|
||||||
|
|
||||||
|
### 마이그레이션 경로 (단계별)
|
||||||
|
|
||||||
|
**Phase 1 — 파일 생성 + 1개 JSP 검증 (위험 최소)**
|
||||||
|
1. `src/main/webapp/css/global.css` 작성
|
||||||
|
2. `login.jsp` 1개에만 `<link>` 태그 추가 + 인라인 토큰 블록 제거
|
||||||
|
3. 브라우저에서 라이트/다크 테마 전환 육안 검증
|
||||||
|
4. 문제 없으면 Phase 2 진행
|
||||||
|
|
||||||
|
**Phase 2 — admin 4개 파일 적용 (`.admin-btn` 중복 제거 포함)**
|
||||||
|
1. `admin-post-categories.jsp`, `admin-unity-feeds.jsp`, `admin-console.jsp`, `admin-jam-list.jsp` 에 `<link>` 추가
|
||||||
|
2. 각 파일의 인라인 `html {}`, `html[data-theme="dark"] {}`, `body {}`, `.admin-btn*` 블록 제거
|
||||||
|
3. admin 기능(등록/수정/삭제) 동작 검증
|
||||||
|
|
||||||
|
**Phase 3 — 나머지 19개 JSP 일괄 적용**
|
||||||
|
1. 남은 파일에 `<link>` 추가 + 인라인 토큰 제거
|
||||||
|
2. 각 페이지 라이트/다크 테마 시각 검증
|
||||||
|
|
||||||
|
### 위험 평가
|
||||||
|
|
||||||
|
| 위험 | 가능성 | 대응 |
|
||||||
|
|---|---|---|
|
||||||
|
| 일부 JSP가 인라인으로 토큰 값을 오버라이드하고 있다면 global.css 도입 후 이전과 동일 동작 | 낮음 (토큰 값 동일 확인됨) | Phase 1 검증 후 진행 |
|
||||||
|
| 특정 파일의 인라인 `html {}` 에 global.css에 없는 추가 변수 존재 (예: `--radius`, `--webgl-bg`) | 있음 (game-detail.jsp 확인) | 파일 전용 변수는 인라인 유지 — 중앙화 대상은 공통 8+4개 토큰만 |
|
||||||
|
| Tomcat DefaultServlet이 `/css/` 경로를 차단하는 보안 필터 존재 | 매우 낮음 | `/images/logo.png` 가 이미 동일 방식으로 서빙 중 — 검증됨 |
|
||||||
|
| FOUC: CSS 로드 전 테마 변수 미적용 깜빡임 | 낮음 | `theme-init.jsp`가 `<head>` 최상단에서 `data-theme` 속성을 먼저 설정하고 있으므로 CSS 변수 로드 타이밍이 맞음 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 권고 2 — JS 공용 유틸리티 분리 (P1)
|
||||||
|
|
||||||
|
### 접근
|
||||||
|
|
||||||
|
`src/main/webapp/js/bibimbap-utils.js` 를 신규 생성하고 admin 4개 파일의 공통 함수(`post`, `handleResult`, `handleError`)를 이 파일로 추출한다. 이 파일은 `window.BibimbapUtils` 네임스페이스에 노출한다.
|
||||||
|
|
||||||
|
`notify()`와 `openModal()` 은 title이 파일마다 달라 단순 통합이 불가능하므로 별도 전략을 쓴다(권고 3에서 다룸).
|
||||||
|
|
||||||
|
각 JSP에서는 `</body>` 직전에 `<script src="${pageContext.request.contextPath}/js/bibimbap-utils.js"></script>` 를 삽입하고, 기존 인라인 함수 선언을 제거한다.
|
||||||
|
|
||||||
|
### 파일 경로 및 API 계약 (함수 시그니처)
|
||||||
|
|
||||||
|
**신규 파일**: `src/main/webapp/js/bibimbap-utils.js`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.BibimbapUtils = (function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSRF 토큰을 포함한 application/x-www-form-urlencoded POST 요청을 전송한다.
|
||||||
|
*
|
||||||
|
* @param {string} url - 요청 대상 URL
|
||||||
|
* @param {URLSearchParams|null} params - 요청 바디 파라미터 (null 이면 빈 URLSearchParams 사용)
|
||||||
|
* @param {string} csrfToken - X-CSRF-Token 헤더에 설정할 토큰 값
|
||||||
|
* @returns {Promise<Response>}
|
||||||
|
*/
|
||||||
|
function post(url, params, csrfToken) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch Response가 ok이면 location.reload(), 아니면 오류 메시지를 콜백으로 전달한다.
|
||||||
|
*
|
||||||
|
* @param {Response} res - fetch가 반환한 Response 객체
|
||||||
|
* @param {function(string)} onError - 오류 메시지 문자열을 받는 콜백
|
||||||
|
*/
|
||||||
|
function handleResult(res, onError) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 네트워크/예외 오류 발생 시 오류 메시지를 콜백으로 전달한다.
|
||||||
|
*
|
||||||
|
* @param {function(string)} onError - 오류 메시지 문자열을 받는 콜백
|
||||||
|
* @returns {function} catch 핸들러로 사용 가능한 함수
|
||||||
|
*/
|
||||||
|
function makeErrorHandler(onError) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
return { post: post, handleResult: handleResult, makeErrorHandler: makeErrorHandler };
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
**시그니처 결정 이유**:
|
||||||
|
|
||||||
|
- `post(url, params, csrfToken)`: 기존 admin 4개 파일의 `post()` 는 클로저로 `CSRF_TOKEN` 변수를 캡처하는 방식이었다. 전역 파일로 추출하면 클로저 캡처가 불가능하므로 `csrfToken`을 명시 파라미터로 받는다. 호출부가 항상 토큰을 직접 넘기므로 의도가 코드에 드러나고 타입 검사도 명확해진다.
|
||||||
|
|
||||||
|
- `handleResult(res, onError)`: 기존 구현은 `notify()`를 직접 호출했다. `notify()`는 파일마다 title이 달라 전역화 불가능하므로, 대신 오류 메시지 문자열을 콜백으로 전달하는 방식으로 변경한다. 호출부가 메시지를 받아 직접 표시하므로 유연성과 분리가 동시에 달성된다.
|
||||||
|
|
||||||
|
- `makeErrorHandler(onError)`: 기존 `handleError()`는 매직 문자열 `'요청 중 오류가 발생했습니다.'` 를 하드코딩했다. 유틸리티 레벨에서 특정 언어 문자열을 결정하지 않도록 콜백으로 위임한다.
|
||||||
|
|
||||||
|
**inflate 경고**: `post()` 의 세 번째 파라미터 `csrfToken`이 항상 사용되는지 구현 시 확인 필요. 만약 호출부 중 하나라도 빈 문자열을 넘기는 패턴이 발견되면 `BibimbapCsrf.token()` 통합을 고려한다 (concerns 참조).
|
||||||
|
|
||||||
|
### 마이그레이션 경로
|
||||||
|
|
||||||
|
1. `bibimbap-utils.js` 작성 (3개 함수 포함)
|
||||||
|
2. `admin-post-categories.jsp` 에 script 태그 추가 + 인라인 `post`, `handleResult`, `handleError` 함수 제거. `BibimbapUtils.post(...)`, `BibimbapUtils.handleResult(...)`, `BibimbapUtils.makeErrorHandler(...)` 로 호출부 수정
|
||||||
|
3. 기능 검증 후 나머지 admin 3개 파일에 동일 적용
|
||||||
|
|
||||||
|
### 위험 평가
|
||||||
|
|
||||||
|
| 위험 | 가능성 | 대응 |
|
||||||
|
|---|---|---|
|
||||||
|
| admin 파일마다 `post()` 구현이 미묘하게 달라 통합 시 동작 변경 | 중간 | 통합 전 4개 파일 `post()` 구현 3-way diff 수행 (concerns 등록) |
|
||||||
|
| `handleResult` 시그니처 변경으로 기존 호출부 수정 필요 | 확실 | 마이그레이션 범위를 admin 4개로 한정, 변경이 넓지 않음 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 권고 3 — 모달 사용 패턴 표준화 (P1)
|
||||||
|
|
||||||
|
### 접근
|
||||||
|
|
||||||
|
**표준 패턴 채택**: `BibimbapModal` API (`window.BibimbapModal.alert / .confirm / .prompt`) 를 직접 호출하는 인라인 패턴을 표준으로 정한다. 기존 헬퍼 래퍼(`openModal()`, `notify()`)는 더 이상 신규 작성하지 않는다.
|
||||||
|
|
||||||
|
**패턴 A (openModal 래퍼) 처리**: login/signup/game-register/profile 4개 파일의 `openModal()` 로컬 함수를 제거하고 호출부를 `window.BibimbapModal.alert({...})` 직접 호출로 대체한다. `BibimbapModal` 미존재 폴백은 `else { alert(message); }` 로 단순화한다.
|
||||||
|
|
||||||
|
**패턴 B (notify 래퍼) 처리**: admin 4개 파일의 `notify()` 를 제거한다. 권고 2의 `BibimbapUtils.handleResult(res, onError)` 에서 `onError` 콜백이 `BibimbapModal.alert` 를 직접 호출하도록 호출부를 작성한다. 이렇게 하면 `notify()` 래퍼 없이도 동일 효과를 얻는다.
|
||||||
|
|
||||||
|
**패턴 C (인라인 직접 체크) 처리**: posts-form/recruit-form/game-detail 은 이미 인라인이므로 현재 형태 유지. 다만 `game-detail.jsp` 내 `notifyError()` / `confirmAction()` 헬퍼(라인 1553-1561)는 파일 전용이므로 제거하지 않는다. 단일 파일에서의 로컬 헬퍼는 허용.
|
||||||
|
|
||||||
|
**window.confirm 직접 사용 수정 (보안/일관성)**:
|
||||||
|
- `admin-post-categories.jsp:323` 의 `window.confirm()` → `BibimbapModal.confirm({...})` 으로 변경
|
||||||
|
- `admin-unity-feeds.jsp:401` 의 `window.confirm()` → `BibimbapModal.confirm({...})` 으로 변경
|
||||||
|
|
||||||
|
이 두 곳은 삭제 확인 흐름이므로 `onConfirm` 콜백에 실제 삭제 로직을 이동한다.
|
||||||
|
|
||||||
|
### 표준 패턴 예시 (참고용)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 알림 (확인 버튼만)
|
||||||
|
if (window.BibimbapModal) {
|
||||||
|
window.BibimbapModal.alert({ title: '제목', message: '내용', confirmText: '확인' });
|
||||||
|
} else {
|
||||||
|
alert('내용');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 확인/취소 대화
|
||||||
|
if (window.BibimbapModal) {
|
||||||
|
window.BibimbapModal.confirm({
|
||||||
|
title: '삭제 확인',
|
||||||
|
message: '삭제하시겠습니까?',
|
||||||
|
onConfirm: function () { /* 삭제 로직 */ }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (window.confirm('삭제하시겠습니까?')) { /* 삭제 로직 */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 파일 영향 맵
|
||||||
|
|
||||||
|
| 변경 유형 | 경로 | 역할 |
|
||||||
|
|---|---|---|
|
||||||
|
| 수정 — openModal 제거 | login.jsp, signup.jsp, game-register.jsp, profile.jsp | 패턴 A → 직접 호출 |
|
||||||
|
| 수정 — notify 제거 | admin-console.jsp, admin-jam-list.jsp, admin-post-categories.jsp, admin-unity-feeds.jsp | 패턴 B → 콜백 방식 |
|
||||||
|
| 수정 — window.confirm 교체 | admin-post-categories.jsp:323, admin-unity-feeds.jsp:401 | BibimbapModal.confirm 사용 |
|
||||||
|
| 유지 — 변경 없음 | game-detail.jsp (notifyError/confirmAction), posts-form.jsp, recruit-form.jsp | 파일 전용 헬퍼 허용 |
|
||||||
|
|
||||||
|
### 마이그레이션 경로
|
||||||
|
|
||||||
|
1. `admin-post-categories.jsp` + `admin-unity-feeds.jsp` 의 `window.confirm` 2곳 먼저 수정 (보안 갭과 연결)
|
||||||
|
2. login/signup openModal 제거 + 직접 호출 전환
|
||||||
|
3. game-register/profile 동일 처리
|
||||||
|
4. admin 4개 파일 notify 제거 (권고 2와 동시 진행)
|
||||||
|
|
||||||
|
### 위험 평가
|
||||||
|
|
||||||
|
| 위험 | 가능성 | 대응 |
|
||||||
|
|---|---|---|
|
||||||
|
| BibimbapModal이 modal.jsp 로드 전에 JS가 실행되는 경우 | 낮음 (header.jsp가 modal.jsp를 항상 include) | 변경 없이 현 구조 유지 |
|
||||||
|
| window.confirm 삭제 시 콜백 이동 누락으로 삭제 로직 미실행 | 중간 | 각 파일 수정 후 삭제 기능 수동 검증 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 권고 4 — 폼 제출 유틸리티 + CSRF 갭 수정 (P0 — 보안)
|
||||||
|
|
||||||
|
### 접근
|
||||||
|
|
||||||
|
**즉시 수정 (CSRF 갭)**: `recruit-form.jsp` 의 fetch 호출부에서 `BibimbapCsrf` 미존재 폴백(라인 390-394)이 CSRF 토큰을 전혀 포함하지 않는 문제를 수정한다. 두 가지 방법 중 **방법 A를 채택**한다.
|
||||||
|
|
||||||
|
**방법 A (채택)**: 폴백 브랜치에 `hidden input`에서 추출한 토큰을 직접 삽입한다.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// recruit-form.jsp 수정안
|
||||||
|
var csrfToken = (document.querySelector('input[name="_csrf"]') || {}).value || '';
|
||||||
|
fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: window.BibimbapCsrf ? window.BibimbapCsrf.headers({
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}) : {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRF-Token': csrfToken // 갭 수정
|
||||||
|
},
|
||||||
|
body: body
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**전제**: `recruit-form.jsp` 에 `<input type="hidden" name="_csrf" value="...">` hidden input을 추가해야 한다. 현재 `recruit-form.jsp` 에는 이 hidden input이 없다. 추가하면 `new FormData(form)` 이 자동으로 `_csrf` 파라미터를 포함하므로 서버사이드 form 파라미터 검증도 함께 강화된다.
|
||||||
|
|
||||||
|
**방법 B (미채택)**: `theme-init.jsp` 의 `BibimbapCsrf` 를 항상 신뢰하여 폴백 분기 자체를 제거. 단, `BibimbapCsrf` 미존재 케이스를 완전히 제거하면 `theme-init.jsp` 가 로드 실패 시 CSRF 토큰이 아예 없어지는 더 큰 갭이 생긴다. 따라서 채택하지 않는다.
|
||||||
|
|
||||||
|
**401 리다이렉트 불일치 수정**: `recruit-form.jsp` 의 401 처리를 `posts-form.jsp` 와 동일하게 `redirectLogin` 함수로 분리한다.
|
||||||
|
|
||||||
|
**성공 시 폴백 불일치 수정**: `recruit-form.jsp:419-421` 의 `alert(...)` 호출을 `posts-form.jsp:260-261` 패턴에 맞게 `alert` 없이 `go()` 만 호출하도록 변경한다.
|
||||||
|
|
||||||
|
### 파일 영향 맵
|
||||||
|
|
||||||
|
| 변경 유형 | 경로 | 역할 |
|
||||||
|
|---|---|---|
|
||||||
|
| 수정 (보안) | recruit-form.jsp | hidden _csrf input 추가 + 폴백 브랜치 토큰 삽입 |
|
||||||
|
| 수정 (일관성) | recruit-form.jsp | 401 처리 redirectLogin 함수 분리 |
|
||||||
|
| 수정 (일관성) | recruit-form.jsp | 성공 폴백 alert 제거 |
|
||||||
|
| 변경 없음 | posts-form.jsp | 현행 유지 (기준 파일) |
|
||||||
|
|
||||||
|
### 마이그레이션 경로
|
||||||
|
|
||||||
|
1. `recruit-form.jsp` 에 `<input type="hidden" name="_csrf" value="...">` 추가 (JSP EL 또는 request attribute 사용)
|
||||||
|
2. JS 폴백 브랜치에 `X-CSRF-Token` 헤더 추가
|
||||||
|
3. 401 처리 함수 분리
|
||||||
|
4. 성공 폴백 통일
|
||||||
|
5. 실제 폼 제출(등록) + 401 시나리오(로그아웃 후 제출) 수동 검증
|
||||||
|
|
||||||
|
### 위험 평가
|
||||||
|
|
||||||
|
| 위험 | 가능성 | 대응 |
|
||||||
|
|---|---|---|
|
||||||
|
| hidden _csrf input 추가 시 서버 컨트롤러가 기대하는 파라미터명 불일치 | 낮음 | posts-form.jsp와 동일 파라미터명 `_csrf` 사용 |
|
||||||
|
| BibimbapCsrf가 항상 존재한다고 가정하고 폴백을 제거하고 싶은 유혹 | 중간 | 방법 B 미채택 이유 참조 — 폴백 브랜치 유지 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 권고 5 — 날짜 포맷 유틸리티 모듈 (P2)
|
||||||
|
|
||||||
|
### 접근
|
||||||
|
|
||||||
|
`game-detail.jsp` 인라인의 `fmtAbsolute`, `fmtRelative`, `buildTimeEl` 세 함수를 `src/main/webapp/js/bibimbap-date.js` 로 추출하고 `window.BibimbapDate` 네임스페이스에 노출한다.
|
||||||
|
|
||||||
|
`game-detail.jsp` 는 `<script src=".../js/bibimbap-date.js"></script>` 를 추가하고 기존 인라인 선언을 제거한다. 다른 detail 페이지(posts-detail, recruit-detail, jam-detail)에서 날짜 포맷이 필요해질 때 이 파일을 include하면 된다 — 현재는 필요하지 않으므로 강제 적용하지 않는다.
|
||||||
|
|
||||||
|
### 파일 경로 및 API 계약 (함수 시그니처)
|
||||||
|
|
||||||
|
**신규 파일**: `src/main/webapp/js/bibimbap-date.js`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.BibimbapDate = (function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISO 8601 문자열을 ko-KR 로케일 절대 날짜/시각 문자열로 변환한다.
|
||||||
|
*
|
||||||
|
* @param {string} iso - ISO 8601 날짜 문자열
|
||||||
|
* @returns {string} - "2026. 6. 30. 오전 10:00:00" 형식, 파싱 실패 시 빈 문자열
|
||||||
|
*/
|
||||||
|
function fmtAbsolute(iso) { ... }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISO 8601 문자열을 상대 시각 문자열로 변환한다 (7일 이내: "n분/시간/일 전", 초과: 절대).
|
||||||
|
*
|
||||||
|
* @param {string} iso - ISO 8601 날짜 문자열
|
||||||
|
* @returns {string} - "3시간 전" 또는 절대 날짜, 파싱 실패 시 빈 문자열
|
||||||
|
*/
|
||||||
|
function fmtRelative(iso) { ... }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <time> 요소를 생성한다. 수정된 항목이면 "(수정됨)" 뱃지를 DocumentFragment로 감싸 반환한다.
|
||||||
|
*
|
||||||
|
* @param {string} iso - ISO 8601 날짜 문자열
|
||||||
|
* @param {boolean} edited - true이면 "(수정됨)" 뱃지 추가
|
||||||
|
* @returns {HTMLElement|DocumentFragment}
|
||||||
|
*/
|
||||||
|
function buildTimeEl(iso, edited) { ... }
|
||||||
|
|
||||||
|
return { fmtAbsolute: fmtAbsolute, fmtRelative: fmtRelative, buildTimeEl: buildTimeEl };
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 파일 영향 맵
|
||||||
|
|
||||||
|
| 변경 유형 | 경로 | 역할 |
|
||||||
|
|---|---|---|
|
||||||
|
| 신규 | src/main/webapp/js/bibimbap-date.js | 날짜 포맷 유틸리티 |
|
||||||
|
| 수정 | src/main/webapp/WEB-INF/views/game-detail.jsp | script 태그 추가 + 인라인 3개 함수 제거 + `BibimbapDate.` 프리픽스 추가 |
|
||||||
|
|
||||||
|
### 마이그레이션 경로
|
||||||
|
|
||||||
|
1. `bibimbap-date.js` 작성 (기존 game-detail.jsp 인라인 코드 그대로 이식)
|
||||||
|
2. `game-detail.jsp` 에 script 태그 추가
|
||||||
|
3. 호출부 3곳(`buildTimeEl` 2건, `fmtAbsolute/fmtRelative` 직접 사용 1건)을 `BibimbapDate.buildTimeEl(...)` 등으로 수정
|
||||||
|
4. 인라인 함수 선언 3개 제거
|
||||||
|
5. game-detail 댓글/리뷰 날짜 표시 수동 검증
|
||||||
|
|
||||||
|
### 위험 평가
|
||||||
|
|
||||||
|
| 위험 | 가능성 | 대응 |
|
||||||
|
|---|---|---|
|
||||||
|
| game-detail.jsp가 매우 큰 파일(2200줄+)이므로 인라인 선언 제거 시 위치 파악 실수 | 중간 | 라인 번호 명시: fmtAbsolute 1516, fmtRelative 1521, buildTimeEl 1538 |
|
||||||
|
| 다른 파일에서 동일 함수명을 전역 선언하는 경우 충돌 | 없음 (조사 결과 game-detail.jsp 단독 선언 확인됨) | 추가 확인 불필요 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 전체 파일 영향 맵
|
||||||
|
|
||||||
|
| 변경 유형 | 경로 | 역할 |
|
||||||
|
|---|---|---|
|
||||||
|
| 신규 | src/main/webapp/css/global.css | 디자인 토큰 + body + admin-btn |
|
||||||
|
| 신규 | src/main/webapp/js/bibimbap-utils.js | post / handleResult / makeErrorHandler |
|
||||||
|
| 신규 | src/main/webapp/js/bibimbap-date.js | 날짜 포맷 3개 함수 |
|
||||||
|
| 수정 (인라인 CSS 제거 + link 추가) | src/main/webapp/WEB-INF/views/*.jsp (23개) | global.css 연동 |
|
||||||
|
| 수정 (script 추가 + 인라인 JS 제거) | admin-console.jsp, admin-jam-list.jsp, admin-post-categories.jsp, admin-unity-feeds.jsp | bibimbap-utils.js 연동 |
|
||||||
|
| 수정 (script 추가 + 인라인 제거) | game-detail.jsp | bibimbap-date.js 연동 |
|
||||||
|
| 수정 (openModal 제거) | login.jsp, signup.jsp, game-register.jsp, profile.jsp | 모달 패턴 표준화 |
|
||||||
|
| 수정 (window.confirm 교체) | admin-post-categories.jsp:323, admin-unity-feeds.jsp:401 | BibimbapModal.confirm 전환 |
|
||||||
|
| 수정 (CSRF 갭 — 보안) | recruit-form.jsp | hidden input 추가 + 폴백 헤더 수정 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 대안 비교
|
||||||
|
|
||||||
|
### CSS 중앙화 방안
|
||||||
|
|
||||||
|
| 안 | 장점 | 단점 | 채택? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A: `src/main/webapp/css/global.css` | 빌드 도구 불필요, Tomcat 즉시 서빙 | 캐시 버스팅 수동 관리 필요 | **채택** |
|
||||||
|
| B: `theme-init.jsp`에 `<style>` 블록 추가 | include 메커니즘 재사용 | JSP 응답마다 CSS가 HTML에 인라인 삽입됨 — 캐싱 불가, 오히려 더 많은 바이트 전송 | 미채택 |
|
||||||
|
| C: `src/main/resources/static/css/global.css` | Spring Boot 자동 서빙 | 해당 경로가 현재 미존재이고 WAR 배포 시 classpath static과 webapp static 혼용이 복잡해짐 | 미채택 |
|
||||||
|
|
||||||
|
### JS 유틸리티 배포 방안
|
||||||
|
|
||||||
|
| 안 | 장점 | 단점 | 채택? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A: `src/main/webapp/js/` 정적 파일 | 빌드 불필요, 직접 서빙 | 캐시 버스팅 수동 관리 필요 | **채택** |
|
||||||
|
| B: JSP include 파일 (`<jsp:include>`) | 기존 include 패턴 일관성 | JS를 JSP로 서빙하면 컨텐츠 타입이 `text/html`로 설정될 위험, 브라우저 모듈 캐싱 불가 | 미채택 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 순서 권고
|
||||||
|
|
||||||
|
```
|
||||||
|
1. [P0-보안] recruit-form.jsp CSRF 갭 수정 — 단독 작업, 위험 낮음
|
||||||
|
2. [P0] global.css 작성 + login.jsp 1개 검증 — Phase 1
|
||||||
|
3. [P0] admin 4개 파일 global.css 적용 — Phase 2
|
||||||
|
4. [P0] 나머지 19개 JSP global.css 적용 — Phase 3
|
||||||
|
5. [P1] bibimbap-utils.js 작성 — admin 파일 수정 전제
|
||||||
|
6. [P1] admin 4개 파일 bibimbap-utils.js 연동 — 5 완료 후
|
||||||
|
7. [P1] window.confirm 2곳 BibimbapModal.confirm 전환 — 6과 동시 가능
|
||||||
|
8. [P1] openModal 패턴 4개 파일 표준화 — 7 완료 후
|
||||||
|
9. [P2] bibimbap-date.js 추출 + game-detail.jsp 연동 — 독립 작업
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 롤아웃 / 마이그레이션
|
||||||
|
|
||||||
|
**역호환**: 이 권고안의 모든 변경은 순수 정적 파일 추가 + JSP 인라인 제거이므로 서버사이드 Java 코드, MyBatis, API 계약에 영향이 없다. 롤백은 추가한 `<link>`, `<script>` 태그를 되돌리고 인라인 블록을 복원하면 된다.
|
||||||
|
|
||||||
|
**캐시 버스팅**: 빌드 시스템이 없으므로 쿼리 스트링 버전 파라미터(`?v=20260630`)를 link/script 태그에 수동으로 붙인다. 초기 배포 시 한 번 붙이고, CSS/JS 변경 시마다 날짜를 업데이트하는 것으로 충분하다.
|
||||||
|
|
||||||
|
**배포 단위**: 정적 파일 추가는 WAR 재배포가 필요하다. 단계별 적용이지만 배포 자체는 1회로 묶어 진행해도 된다.
|
||||||
|
|
||||||
|
**롤백 경로**:
|
||||||
|
1. `global.css` 추가 후 이슈 발생 시 → `<link>` 태그 제거 + git revert
|
||||||
|
2. `bibimbap-utils.js` 이후 이슈 발생 시 → script 태그 제거 + 인라인 함수 복원
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 검증 포인트
|
||||||
|
|
||||||
|
아래 AC는 verification-advisor가 점검한다.
|
||||||
|
|
||||||
|
**AC-1 (P0)**: `src/main/webapp/css/global.css` 파일 존재 확인.
|
||||||
|
|
||||||
|
**AC-2 (P0)**: global.css가 라이트/다크 테마 HTML을 열었을 때 각각 올바른 배경색(`--surface: #faf8f5` / `#121212`)이 적용되는지 브라우저 확인.
|
||||||
|
|
||||||
|
**AC-3 (P0)**: JSP 23개 파일에 `<link rel="stylesheet" ... /css/global.css>` 포함 전수 확인.
|
||||||
|
```
|
||||||
|
grep -rl 'global\.css' src/main/webapp/WEB-INF/views/ | wc -l == 23
|
||||||
|
```
|
||||||
|
*시점 안정성 주의*: 이 카운트는 구현이 완료된 직후 측정해야 한다. 구현 중간에 측정하면 미완료 파일이 포함돼 FAIL로 나올 수 있다. verification은 구현 완료 후 1회 측정한다.
|
||||||
|
|
||||||
|
**AC-4 (P0-보안)**: `recruit-form.jsp` 에 `<input type="hidden" name="_csrf"` 가 존재하고, JS 폴백 브랜치에 `'X-CSRF-Token': csrfToken` 이 포함되어 있음 확인.
|
||||||
|
```
|
||||||
|
grep -c 'X-CSRF-Token' src/main/webapp/WEB-INF/views/recruit-form.jsp >= 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**AC-5 (P1)**: `src/main/webapp/js/bibimbap-utils.js` 파일 존재 + `window.BibimbapUtils` 노출 확인.
|
||||||
|
|
||||||
|
**AC-6 (P1)**: admin 4개 파일에 인라인 `function post(` 선언이 제거되었음 확인.
|
||||||
|
```
|
||||||
|
grep -rl 'function post(' src/main/webapp/WEB-INF/views/admin-*.jsp | wc -l == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**AC-7 (P1)**: `admin-post-categories.jsp:323` 및 `admin-unity-feeds.jsp:401` 위치에 `window.confirm(` 이 더 이상 없음 확인.
|
||||||
|
```
|
||||||
|
grep -c 'window\.confirm' src/main/webapp/WEB-INF/views/admin-post-categories.jsp == 0
|
||||||
|
grep -c 'window\.confirm' src/main/webapp/WEB-INF/views/admin-unity-feeds.jsp == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**AC-8 (P1)**: login/signup/game-register/profile 4개 파일에 `function openModal(` 선언이 제거되었음 확인 (전수 4건 → 0건).
|
||||||
|
```
|
||||||
|
grep -rl 'function openModal(' src/main/webapp/WEB-INF/views/ | wc -l == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**AC-9 (P2)**: `src/main/webapp/js/bibimbap-date.js` 파일 존재 + `window.BibimbapDate` 노출 확인.
|
||||||
|
|
||||||
|
**AC-10 (P2)**: `game-detail.jsp` 인라인에 `function fmtAbsolute(`, `function fmtRelative(`, `function buildTimeEl(` 3개 선언이 모두 제거되었음 확인 (전수 3건 → 0건).
|
||||||
|
```
|
||||||
|
grep -c 'function fmtAbsolute\|function fmtRelative\|function buildTimeEl' src/main/webapp/WEB-INF/views/game-detail.jsp == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**AC-11 (전체)**: 신규 정적 파일 전수 3개 존재 확인.
|
||||||
|
```
|
||||||
|
ls src/main/webapp/css/global.css src/main/webapp/js/bibimbap-utils.js src/main/webapp/js/bibimbap-date.js
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
phase: documentation
|
||||||
|
agent: documentation-advisor
|
||||||
|
agent_version: 1
|
||||||
|
generated_at: 2026-06-30T04:30:00Z
|
||||||
|
concerns:
|
||||||
|
- "theme-init.jsp, header.jsp, footer.jsp 내 CSS 변수 선언 여부가 미탐색 상태로 남아 있음. global.css 도입 전 이중 선언 여부 확인 권장."
|
||||||
|
- "BibimbapCsrf 실제 구현체(별도 JS 파일) 미탐색. headers() 폴백 동작 미확인 상태로 권고 4(CSRF 갭 수정) 구현 전 확인 필요."
|
||||||
|
- "admin 4개 파일의 post() 구현이 실제로 동일한지 정밀 3-way diff 미수행. bibimbap-utils.js 통합 전 수행 권장."
|
||||||
|
concerns_checked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# 문서화 보고
|
||||||
|
|
||||||
|
## 작성/수정된 문서
|
||||||
|
|
||||||
|
| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `docs/analysis/2026-06-30-frontend-component-review.md` | analysis | 신규 생성 | `docs/analysis/index.md` | `docs/analysis/2026-06-16-project-analysis.md` (관련 기존 분석 섹션에서 참조) |
|
||||||
|
| `docs/analysis/index.md` | analysis | 기존 수정 (링크 추가) | — | — |
|
||||||
|
|
||||||
|
## 의사결정 기록 위치
|
||||||
|
|
||||||
|
- 프론트엔드 컴포넌트 개선 권고(CSS 중앙화 방안 A/B/C 비교, JS 유틸리티 배포 방안 A/B 비교, CSRF 갭 수정 방법 A/B 비교)는 design-advisor 산출물(`20260630-113258/artifacts/frontend-design-recommendations.md`)에 기록되어 있으며, 이 분석 문서에서 결론을 인용.
|
||||||
|
- 되돌리기 어려운 아키텍처 결정(ADR)은 이번 작업 범위에 없음 (정적 파일 추가는 가역적 변경).
|
||||||
|
|
||||||
|
## 추후 문서화가 필요한 항목
|
||||||
|
|
||||||
|
- 권고 1(global.css) 구현 완료 후: `docs/changes/` 에 런타임 동작 변경(CSS 서빙 경로 추가) 이력 기록
|
||||||
|
- 권고 4(recruit-form CSRF 갭 수정) 구현 완료 후: `docs/changes/` 에 보안 수정 이력 기록, `docs/security/security-remediation-checklist.md` 해당 항목 완료 표시
|
||||||
|
- 권고 2(bibimbap-utils.js) + 권고 3(모달 표준화) 구현 완료 후: JS 공용 파일 API 계약이 확정되면 `docs/contracts/` 또는 `docs/architecture/` 에 프론트엔드 유틸리티 레이어 설계 기록 고려
|
||||||
|
- 구현 전 3-way diff 수행 결과는 `docs/analysis/` 또는 work-log에 기록 권장
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
schema_version: "2"
|
||||||
|
sid: "20260630-113258"
|
||||||
|
started_at: "2026-06-30T11:32:58"
|
||||||
|
ended_at: ~
|
||||||
|
user_request: >
|
||||||
|
/frontend-design:frontend-design 프론트 코드들 컴포넌트별로 매번 새로
|
||||||
|
구현하는건 없는지 유지 보수, 확장 차원에서 부족한건 없는지 검토하자
|
||||||
|
|
||||||
|
Summary: ~
|
||||||
|
|
||||||
|
Invocations: []
|
||||||
|
|
||||||
|
Decisions: []
|
||||||
|
|
||||||
|
# Advisor Invocation Decision Log
|
||||||
|
# 각 advisor 호출/스킵 판단 즉시 1줄 append
|
||||||
|
- advisor: requirements-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: '요청 명확 — JSP 프론트엔드 컴포넌트 중복/유지보수/확장성 검토'
|
||||||
|
checked_at: "2026-06-30T11:33:00"
|
||||||
|
- advisor: graphify-lookup-advisor
|
||||||
|
decision: call
|
||||||
|
rationale: '기존 그래프 산출물에 프론트엔드 분석 캐시 있을 수 있음'
|
||||||
|
checked_at: "2026-06-30T11:33:00"
|
||||||
|
result: 'miss — JSP/JS/CSS 파일이 graphify 스캔 범위 외'
|
||||||
|
- advisor: research-advisor
|
||||||
|
decision: call
|
||||||
|
rationale: 'graphify miss → 실제 JSP 탐색 필요. 중복 함수/CSS/패턴 발굴'
|
||||||
|
checked_at: "2026-06-30T11:35:00"
|
||||||
|
result: '5개 탐색 완료. CSS 23파일 중복, JS 헬퍼 8중복, CSRF 보안갭, 날짜함수 편재 확인'
|
||||||
|
- advisor: design-advisor
|
||||||
|
decision: call
|
||||||
|
rationale: '발견 사항 기반 개선 권고안 구조화 필요'
|
||||||
|
checked_at: "2026-06-30T11:50:00"
|
||||||
|
result: '5개 권고안 + 파일 영향 맵 + AC 11개 산출 완료'
|
||||||
|
- advisor: implementation-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: '검토 전용 태스크 — 구현은 사용자 결정 후 별도 세션'
|
||||||
|
checked_at: "2026-06-30T12:00:00"
|
||||||
|
- advisor: verification-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: '코드 변경 0건 — AC는 설계 문서에 기록됨'
|
||||||
|
checked_at: "2026-06-30T12:00:00"
|
||||||
|
- advisor: documentation-advisor
|
||||||
|
decision: call
|
||||||
|
rationale: '분석 결과를 docs/analysis/ 에 영구 기록'
|
||||||
|
checked_at: "2026-06-30T12:00:00"
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
---
|
||||||
|
phase: research
|
||||||
|
agent: research-advisor
|
||||||
|
agent_version: 2
|
||||||
|
generated_at: 2026-06-30T02:41:09Z
|
||||||
|
concerns: []
|
||||||
|
source_confidence: high
|
||||||
|
workers_spawned: 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-268` — `redirectLogin` 함수 분리, `onConfirm`에서 호출
|
||||||
|
- `recruit-form.jsp:430-431, 437-439` — 모달 `onConfirm`과 else 분기 양쪽에 각각 인라인 중복 작성
|
||||||
|
- 신뢰도: 확인됨
|
||||||
|
|
||||||
|
4. **수정 모드**: `posts-form.jsp:9-10, 215` — `mode`, `postId` 로 등록/수정 URL 분기. `recruit-form.jsp`는 수정 모드 분기 없음.
|
||||||
|
5. **실시간 미리보기**: `recruit-form.jsp:340-373` — `pairs` 배열 + `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:1878` — `buildTimeEl` 사용 (댓글 항목)
|
||||||
|
- `game-detail.jsp:2183-2186` — `fmtAbsolute`/`fmtRelative` 직접 사용 (리뷰 항목)
|
||||||
|
- 신뢰도: 확인됨
|
||||||
|
|
||||||
|
### 서버사이드 날짜 포매팅
|
||||||
|
- 조사 범위 내 Java Date 포매팅으로 화면 출력하는 경우: 0건
|
||||||
|
- `posts-list.jsp:6-7,19` — `OffsetDateTime` import하지만 커서 URL 생성에만 사용 (화면 출력 아님)
|
||||||
|
- `jam-detail.jsp:442` — `OffsetDateTime.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.jsp`가 `notify(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-1561`에 `notifyError()`, `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.jsp`와 `admin-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.jsp`와 `admin-unity-feeds.jsp`는 알림에는 BibimbapModal 체크를 쓰면서 삭제 확인은 `window.confirm()` 직접 사용 — 같은 파일 내 불일치.
|
||||||
|
- `jam-detail.jsp:128`과 `recruit-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()` 함수가 실제로 다른 구현인지 정밀 비교 미수행
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
---
|
||||||
|
kind: analysis
|
||||||
|
title: 프론트엔드 컴포넌트 중복/유지보수·확장성 검토 (2026-06-30)
|
||||||
|
description: JSP 기반 프론트엔드에서 발견된 CSS 토큰 23개 파일 중복, JS 유틸리티 함수 중복, BibimbapModal 호출 패턴 혼존, recruit-form CSRF 폴백 갭(보안), 날짜 포맷 함수 편재를 분석하고 권고 구현 순서를 기록한다.
|
||||||
|
perspective: neutral
|
||||||
|
valid_starting_point_for: 프론트엔드 중복 제거 작업 착수, CSRF 갭 수정(보안 P0), CSS/JS 공용 파일 설계
|
||||||
|
owner: art
|
||||||
|
stability: snapshot
|
||||||
|
last_reviewed: 2026-06-30
|
||||||
|
source: work-session 20260630-113258 (research-advisor frontend-analysis.md, design-advisor frontend-design-recommendations.md) 에서 distill
|
||||||
|
superseded_note: 후속 결정으로 전제가 바뀌면 이 줄을 갱신한다 — 현재 없음
|
||||||
|
---
|
||||||
|
|
||||||
|
# 프론트엔드 컴포넌트 중복/유지보수·확장성 검토 (2026-06-30)
|
||||||
|
|
||||||
|
> 조사 범위: 27개 JSP 파일 전수 직접 읽기. 모든 사실은 코드 직접 확인(`확인됨`). 미탐색 항목은 [미발견/조사 불충분](#미발견--조사-불충분-항목)에 명시.
|
||||||
|
>
|
||||||
|
> 출처: work-session `20260630-113258` — `research/frontend-analysis.md` (research-advisor) + `artifacts/frontend-design-recommendations.md` (design-advisor).
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
JSP 기반 프론트엔드에서 5개 유형의 중복/이슈가 확인됐다. 가장 심각한 것은 라이트/다크 테마 CSS 변수 블록과 `body` 기본 스타일이 23개 파일에 동일하게 복사된 구조이며, 보안 측면에서는 `recruit-form.jsp`의 CSRF 폴백 분기에서 토큰이 전혀 포함되지 않는 갭이 존재한다.
|
||||||
|
|
||||||
|
전체 27개 JSP 중 26개에 인라인 `<style>` 블록이 존재하며, 누적 인라인 CSS는 5,969줄이다.
|
||||||
|
|
||||||
|
## 발견 이슈 요약 (우선순위 매트릭스)
|
||||||
|
|
||||||
|
| 이슈 | 영향도 | 구현 비용 | 우선순위 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| CSS 토큰 23개 파일 중복 | 높음 (유지보수 23배 비용) | 낮음 (파일 추가 + include 삽입) | P0 |
|
||||||
|
| 폼 제출 CSRF 갭 (recruit-form) | 높음 (보안) | 낮음 (10줄 이내 수정) | P0 — 보안 |
|
||||||
|
| JS 공용 유틸리티 분리 | 중간 (동작 불일치 방지) | 중간 (파일 작성 + 기존 인라인 제거) | P1 |
|
||||||
|
| 모달 패턴 표준화 | 중간 (신규 페이지 기준 제공) | 중간 (패턴 A/B 대체 + admin 2곳 수정) | P1 |
|
||||||
|
| 날짜 포맷 유틸리티 모듈 | 낮음 (예방적 분리) | 낮음 (파일 추출) | P2 |
|
||||||
|
|
||||||
|
## 이슈별 상세
|
||||||
|
|
||||||
|
### CSS 디자인 토큰 23개 파일 중복 (P0)
|
||||||
|
|
||||||
|
#### 현황
|
||||||
|
|
||||||
|
전체 27개 JSP 중 26개에 인라인 `<style>` 블록이 존재한다 (`theme-init.jsp` 제외). 다음 3개 블록이 23개 파일에 동일하게 복사된다.
|
||||||
|
|
||||||
|
| 중복 패턴 | 반복 파일 수 | 대표 위치 |
|
||||||
|
|---|---|---|
|
||||||
|
| `html {}` 라이트 테마 CSS 변수 선언 (8개 색상 토큰) | 23개 | `admin-console.jsp:38`, `jam-list.jsp:21`, `login.jsp:12` |
|
||||||
|
| `html[data-theme="dark"] {}` 다크 테마 CSS 변수 선언 | 23개 | `admin-console.jsp:51`, `posts-list.jsp:41`, `recruit-list.jsp:34` |
|
||||||
|
| `body {}` 기본 리셋 + 폰트 + 배경 | 23개 | `admin-console.jsp:62`, `jam-list.jsp:41`, `terms.jsp:29` |
|
||||||
|
|
||||||
|
핵심 공통 토큰: `--surface`, `--card-bg`, `--text`, `--text-muted`, `--accent`, `--accent-soft`, `--border`, `--shadow` (라이트) / `#121212`, `#1e1e1e`, `#ece8e1`, `#a39e96`, `rgba(255,255,255,0.1)` (다크).
|
||||||
|
|
||||||
|
추가 중복: `.admin-btn` / `.admin-btn--primary` / `.admin-btn--danger` 컴포넌트 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`).
|
||||||
|
|
||||||
|
`safe-area` padding 패턴(`padding: 1.5rem max(1rem, env(safe-area-inset-left)) 3rem max(1rem, env(safe-area-inset-right))`)은 25개 파일에 반복되나, `max-width` 값은 48/56/64/72/78rem으로 파일별 상이하다.
|
||||||
|
|
||||||
|
#### 충돌/갭
|
||||||
|
|
||||||
|
- CSS 변수 선언이 파일마다 포함하는 변수 수가 8~14개로 약간씩 달라, 특정 파일에서 일부 변수가 미선언된 상태일 수 있다.
|
||||||
|
- `jam-detail.jsp:128`과 `recruit-detail.jsp:116`의 `.detail-button--primary color` 값 불일치 (`#1a1a1a` vs `var(--button-text)`).
|
||||||
|
|
||||||
|
#### 권고 (design-advisor)
|
||||||
|
|
||||||
|
`src/main/webapp/css/global.css` 정적 파일을 신규 생성하여 공통 토큰, `body` 기본 스타일, `.admin-btn` 컴포넌트를 선언한다. 각 JSP `<head>`에 `<link rel="stylesheet" href="${pageContext.request.contextPath}/css/global.css">` 를 삽입하고 인라인 중복 블록을 제거한다.
|
||||||
|
|
||||||
|
**포함 대상**: 공통 8+4개 토큰, `body` 기본 스타일, `.admin-btn*` 컴포넌트.
|
||||||
|
|
||||||
|
**포함하지 않는 것**: 각 JSP 고유 레이아웃 클래스, `max-width` 값(파일마다 다름), `--danger` 변수(`admin-unity-feeds.jsp` 단독 사용), 페이지 전용 컴포넌트 CSS.
|
||||||
|
|
||||||
|
**배포 경로**: `src/main/webapp/` 하위 정적 파일은 Tomcat DefaultServlet이 직접 서빙한다. `src/main/resources/static/` 경로 불필요.
|
||||||
|
|
||||||
|
**마이그레이션 3단계**: Phase 1(파일 생성 + login.jsp 1개 검증) → Phase 2(admin 4개 파일) → Phase 3(나머지 19개 JSP 일괄).
|
||||||
|
|
||||||
|
**위험**: `game-detail.jsp`처럼 `--radius`, `--webgl-bg` 등 파일 전용 변수가 있는 경우 해당 변수는 인라인 유지. FOUC는 `theme-init.jsp`가 `<head>` 최상단에서 `data-theme` 속성을 먼저 설정하므로 낮음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### JS 유틸리티 함수 중복 (P1)
|
||||||
|
|
||||||
|
#### 현황
|
||||||
|
|
||||||
|
admin 4개 파일(`admin-console.jsp`, `admin-jam-list.jsp`, `admin-post-categories.jsp`, `admin-unity-feeds.jsp`)에 동일 패턴의 함수가 각각 선언된다.
|
||||||
|
|
||||||
|
| 함수 | 파일 | 영향도 |
|
||||||
|
|---|---|---|
|
||||||
|
| `notify(message)` | admin 4개 파일 각각 선언 | Medium |
|
||||||
|
| `post(url, params)` | 동일 4개 파일. 구현이 약간씩 다름 (params 처리, _csrf 추가 여부) | High |
|
||||||
|
| `handleResult(res)`, `handleError()` | 동일 4개 파일 동일 패턴 | Medium |
|
||||||
|
|
||||||
|
특히 `post()` 함수는 파일마다 구현이 미묘하게 달라 동작 불일치 위험이 내재한다.
|
||||||
|
|
||||||
|
#### 권고 (design-advisor)
|
||||||
|
|
||||||
|
`src/main/webapp/js/bibimbap-utils.js` 신규 생성. `window.BibimbapUtils` 네임스페이스에 `post(url, params, csrfToken)`, `handleResult(res, onError)`, `makeErrorHandler(onError)` 를 노출한다.
|
||||||
|
|
||||||
|
**시그니처 변경 이유**:
|
||||||
|
- `post(url, params, csrfToken)`: 기존 클로저 캡처 방식 대신 토큰을 명시 파라미터로 받는다.
|
||||||
|
- `handleResult(res, onError)`: 기존의 `notify()` 직접 호출 대신 오류 메시지 콜백으로 위임한다 (title이 파일마다 달라 전역화 불가).
|
||||||
|
- `makeErrorHandler(onError)`: 매직 문자열 하드코딩 제거.
|
||||||
|
|
||||||
|
**`notify()` / `openModal()` 통합 불가**: title이 파일마다 달라 단순 통합이 불가능하므로 권고 3(모달 표준화)에서 별도 처리.
|
||||||
|
|
||||||
|
**위험**: admin 파일마다 `post()` 구현이 미묘하게 달라 통합 시 동작 변경 가능. 통합 전 4개 파일 3-way diff 수행 권고.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BibimbapModal 호출 패턴 혼존 (P1)
|
||||||
|
|
||||||
|
#### 현황
|
||||||
|
|
||||||
|
`BibimbapModal` API는 `modal.jsp:174-238`에 단일 `<div id="site-modal">` 재사용 방식으로 구현되어 있으며, `alert`, `confirm`, `prompt` 메서드를 제공한다. 그러나 호출 패턴이 3가지로 혼존한다.
|
||||||
|
|
||||||
|
**패턴 A — `openModal()` 헬퍼 래퍼 (4개 파일)**
|
||||||
|
|
||||||
|
`login.jsp:256`, `signup.jsp:269`, `game-register.jsp:490`, `profile.jsp:539` 가 동일 시그니처의 `openModal(title, message, confirmText, onConfirm)` 로컬 함수를 각자 정의.
|
||||||
|
|
||||||
|
**패턴 B — `notify()` 헬퍼 래퍼 (4개 admin 파일)**
|
||||||
|
|
||||||
|
`admin-jam-list.jsp:389`, `admin-console.jsp:401`, `admin-post-categories.jsp:251`, `admin-unity-feeds.jsp:305` 가 `notify(message)` 로컬 함수를 각자 정의 (alert 전용, title 하드코딩).
|
||||||
|
|
||||||
|
**패턴 C — 인라인 직접 체크 (3개 파일)**
|
||||||
|
|
||||||
|
`recruit-form.jsp:411, 424`, `posts-form.jsp:252, 269`, `game-detail.jsp:1425, 1439, 1474, 1482, 1553, 1558` 가 호출 지점마다 인라인으로 체크.
|
||||||
|
|
||||||
|
**특이 케이스**: `game-detail.jsp:1553-1561`에 `notifyError()`, `confirmAction()` 헬퍼를 별도 정의하면서, 동일 파일 `1425-1449`에 인라인 체크도 병존하여 단일 파일 내 두 패턴이 혼재한다.
|
||||||
|
|
||||||
|
**`window.confirm` 직접 사용**: `admin-post-categories.jsp:323`과 `admin-unity-feeds.jsp:401`에서 삭제 확인 대화상자에 `window.confirm()`을 직접 사용. 알림에는 `notify()` 헬퍼(BibimbapModal 체크)를 쓰면서 삭제 확인에는 직접 사용하는 동일 파일 내 불일치.
|
||||||
|
|
||||||
|
#### 권고 (design-advisor)
|
||||||
|
|
||||||
|
**표준 패턴**: `window.BibimbapModal.alert / .confirm / .prompt` 직접 호출. 기존 헬퍼 래퍼는 신규 작성 금지.
|
||||||
|
|
||||||
|
- 패턴 A: login/signup/game-register/profile 4개 파일의 `openModal()` 제거 → 직접 호출로 대체.
|
||||||
|
- 패턴 B: admin 4개 파일의 `notify()` 제거 → 권고 2의 `BibimbapUtils.handleResult(res, onError)` 의 `onError` 콜백에서 직접 호출.
|
||||||
|
- 패턴 C: posts-form/recruit-form/game-detail 현재 형태 유지. `game-detail.jsp` 내 `notifyError()` / `confirmAction()` 파일 전용 헬퍼는 제거하지 않는다 (단일 파일 로컬 헬퍼 허용).
|
||||||
|
- `window.confirm` 2곳을 `BibimbapModal.confirm({...})` 으로 교체.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### recruit-form CSRF 폴백 갭 (P0 — 보안)
|
||||||
|
|
||||||
|
#### 현황
|
||||||
|
|
||||||
|
`posts-form.jsp`와 `recruit-form.jsp`는 fetch 기반 비동기 폼 제출, `checkValidity()` 검증, `BibimbapModal.alert` 처리, `BibimbapCsrf` CSRF 헤더 주입이라는 동일 골격을 공유하지만, CSRF 처리에 불일치가 존재한다.
|
||||||
|
|
||||||
|
- `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` 미존재 시 폴백 헤더에 CSRF 토큰이 전혀 포함되지 않는 보안 갭.
|
||||||
|
|
||||||
|
추가 불일치:
|
||||||
|
|
||||||
|
| 항목 | posts-form.jsp | recruit-form.jsp |
|
||||||
|
|---|---|---|
|
||||||
|
| 성공 시 폴백 | `} else { go(); }` (alert 없음) | `} else { alert('팀원 모집글이 등록되었습니다.'); go(); }` |
|
||||||
|
| 401 처리 | `redirectLogin` 함수 분리, `onConfirm`에서 호출 | 모달 `onConfirm`과 else 분기 양쪽에 인라인 중복 |
|
||||||
|
| 수정 모드 | `mode`, `postId` 로 등록/수정 URL 분기 있음 | 수정 모드 분기 없음 |
|
||||||
|
|
||||||
|
#### 권고 (design-advisor)
|
||||||
|
|
||||||
|
**즉시 수정 (방법 A 채택)**: `recruit-form.jsp`에 `<input type="hidden" name="_csrf" value="...">` hidden input 추가 + JS 폴백 분기에 `X-CSRF-Token: csrfToken` 헤더 삽입.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 수정안 요지
|
||||||
|
var csrfToken = (document.querySelector('input[name="_csrf"]') || {}).value || '';
|
||||||
|
// ... BibimbapCsrf 미존재 시 폴백 헤더에 'X-CSRF-Token': csrfToken 추가
|
||||||
|
```
|
||||||
|
|
||||||
|
hidden input 추가 시 `new FormData(form)` 이 자동으로 `_csrf` 파라미터를 포함하므로 서버사이드 form 파라미터 검증도 함께 강화된다.
|
||||||
|
|
||||||
|
**방법 B (미채택)**: BibimbapCsrf를 항상 신뢰하여 폴백 분기 자체 제거 — BibimbapCsrf 로드 실패 시 더 큰 갭이 생기므로 채택하지 않는다.
|
||||||
|
|
||||||
|
**추가 수정**: 401 처리를 `redirectLogin` 함수로 분리, 성공 폴백 alert 제거.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 날짜 포맷 함수 편재 (P2)
|
||||||
|
|
||||||
|
#### 현황
|
||||||
|
|
||||||
|
날짜/시간 포맷 JavaScript 함수가 `game-detail.jsp` 단 하나의 파일에만 존재한다. 조사 범위 7개 파일(`posts-detail`, `recruit-detail`, `jam-detail`, `posts-list`, `jam-list`, `recruit-list`, `game-detail`) 중 나머지 6개에는 날짜 포맷 JS 함수가 전혀 없다.
|
||||||
|
|
||||||
|
| 함수 | 위치 | 구현 방식 |
|
||||||
|
|---|---|---|
|
||||||
|
| `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>` 요소 + "(수정됨)" 뱃지 반환 |
|
||||||
|
|
||||||
|
사용 위치: `game-detail.jsp:1878` (댓글), `game-detail.jsp:2183-2186` (리뷰).
|
||||||
|
|
||||||
|
`Intl.RelativeTimeFormat` / `Intl.DateTimeFormat` 직접 사용은 조사 범위 7개 파일 전체에서 0 hit.
|
||||||
|
|
||||||
|
**구조적 위험**: 다른 detail 페이지가 동일 기능이 필요해지면 복사 중복 발생 구조.
|
||||||
|
|
||||||
|
#### 권고 (design-advisor)
|
||||||
|
|
||||||
|
`src/main/webapp/js/bibimbap-date.js` 신규 생성. `window.BibimbapDate` 네임스페이스에 3개 함수 노출. `game-detail.jsp`는 script 태그 추가 + 인라인 선언 제거 + `BibimbapDate.` 프리픽스 적용.
|
||||||
|
|
||||||
|
현재 다른 detail 페이지에서 날짜 포맷이 필요하지 않으므로 강제 적용하지 않는다. 필요 시 include.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 권고 구현 순서
|
||||||
|
|
||||||
|
```
|
||||||
|
1. [P0-보안] recruit-form.jsp CSRF 갭 수정 — 단독 작업, 위험 낮음
|
||||||
|
2. [P0] global.css 작성 + login.jsp 1개 검증 — Phase 1
|
||||||
|
3. [P0] admin 4개 파일 global.css 적용 — Phase 2
|
||||||
|
4. [P0] 나머지 19개 JSP global.css 적용 — Phase 3
|
||||||
|
5. [P1] bibimbap-utils.js 작성 — admin 파일 수정 전제
|
||||||
|
6. [P1] admin 4개 파일 bibimbap-utils.js 연동 — 5 완료 후
|
||||||
|
7. [P1] window.confirm 2곳 BibimbapModal.confirm 전환 — 6과 동시 가능
|
||||||
|
8. [P1] openModal 패턴 4개 파일 표준화 — 7 완료 후
|
||||||
|
9. [P2] bibimbap-date.js 추출 + game-detail.jsp 연동 — 독립 작업
|
||||||
|
```
|
||||||
|
|
||||||
|
**검증 기준(AC)**: 구현 완료 후 design-advisor 산출물의 AC-1~AC-11 항목을 기준으로 verification-advisor가 점검한다.
|
||||||
|
|
||||||
|
**롤백 경로**: 추가한 `<link>`, `<script>` 태그를 되돌리고 인라인 블록 복원. 서버사이드 Java 코드, MyBatis, API 계약에 영향 없음.
|
||||||
|
|
||||||
|
**캐시 버스팅**: 빌드 시스템 없음 → 쿼리 스트링 버전 파라미터(`?v=20260630`) 수동 관리.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 미발견 / 조사 불충분 항목
|
||||||
|
|
||||||
|
- `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()` 함수가 실제로 다른 구현인지 정밀 비교 미수행
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 관련 파일
|
||||||
|
|
||||||
|
### 분석 대상 (주요)
|
||||||
|
|
||||||
|
| 파일 | 이슈 |
|
||||||
|
|---|---|
|
||||||
|
| `src/main/webapp/WEB-INF/views/recruit-form.jsp` | CSRF 폴백 갭 (P0-보안), 401 처리 불일치 |
|
||||||
|
| `src/main/webapp/WEB-INF/views/posts-form.jsp` | CSRF 기준 파일 (변경 없음) |
|
||||||
|
| `src/main/webapp/WEB-INF/views/admin-console.jsp` | CSS 토큰 중복, JS 함수 중복, notify 패턴 |
|
||||||
|
| `src/main/webapp/WEB-INF/views/admin-jam-list.jsp` | CSS 토큰 중복, JS 함수 중복, notify 패턴 |
|
||||||
|
| `src/main/webapp/WEB-INF/views/admin-post-categories.jsp` | CSS 토큰 중복, JS 함수 중복, window.confirm 직접 사용 |
|
||||||
|
| `src/main/webapp/WEB-INF/views/admin-unity-feeds.jsp` | CSS 토큰 중복, JS 함수 중복, window.confirm 직접 사용 |
|
||||||
|
| `src/main/webapp/WEB-INF/views/game-detail.jsp` | 날짜 함수 편재, 모달 패턴 혼재 |
|
||||||
|
| `src/main/webapp/WEB-INF/views/login.jsp` | openModal 패턴 A |
|
||||||
|
| `src/main/webapp/WEB-INF/views/signup.jsp` | openModal 패턴 A |
|
||||||
|
| `src/main/webapp/WEB-INF/views/game-register.jsp` | openModal 패턴 A |
|
||||||
|
| `src/main/webapp/WEB-INF/views/profile.jsp` | openModal 패턴 A |
|
||||||
|
|
||||||
|
### 신규 생성 예정 (권고안)
|
||||||
|
|
||||||
|
| 파일 | 역할 |
|
||||||
|
|---|---|
|
||||||
|
| `src/main/webapp/css/global.css` | 디자인 토큰 + body 기본 스타일 + admin-btn 컴포넌트 |
|
||||||
|
| `src/main/webapp/js/bibimbap-utils.js` | post / handleResult / makeErrorHandler 공용 유틸리티 |
|
||||||
|
| `src/main/webapp/js/bibimbap-date.js` | fmtAbsolute / fmtRelative / buildTimeEl 날짜 포맷 유틸리티 |
|
||||||
|
|
||||||
|
### 관련 기존 분석
|
||||||
|
|
||||||
|
- [2026-06-16-project-analysis.md](./2026-06-16-project-analysis.md) — bibimbap 전면 코드 분석. D2 보안 섹션에서 CSRF 더블서밋 기초 확인. 이 문서의 recruit-form CSRF 갭은 해당 분석 이후 발견된 추가 이슈다.
|
||||||
|
|
@ -5,3 +5,4 @@
|
||||||
## 목록
|
## 목록
|
||||||
|
|
||||||
- [2026-06-16-project-analysis.md](./2026-06-16-project-analysis.md) — bibimbap 전면 코드 분석(D1 아키텍처/D2 보안/D3 품질·기술부채/D4 도메인). 보안 기초는 견고(SQLi 0·PBKDF2·CSRF 더블서밋)하나 프로토타입 잔재 dead code·미완성 기능·서비스 레이어 부재가 주요 부채. `perspective: neutral`, work-session 20260616-111711 distill.
|
- [2026-06-16-project-analysis.md](./2026-06-16-project-analysis.md) — bibimbap 전면 코드 분석(D1 아키텍처/D2 보안/D3 품질·기술부채/D4 도메인). 보안 기초는 견고(SQLi 0·PBKDF2·CSRF 더블서밋)하나 프로토타입 잔재 dead code·미완성 기능·서비스 레이어 부재가 주요 부채. `perspective: neutral`, work-session 20260616-111711 distill.
|
||||||
|
- [2026-06-30-frontend-component-review.md](./2026-06-30-frontend-component-review.md) — JSP 프론트엔드 컴포넌트 중복/유지보수·확장성 검토. CSS 토큰 23개 파일 중복(P0)·JS 유틸리티 함수 중복(P1)·BibimbapModal 호출 패턴 혼존(P1)·recruit-form CSRF 폴백 갭(P0-보안)·날짜 포맷 함수 편재(P2). work-session 20260630-113258 distill.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue