Conversation
M14 바닐라 데모 프론트를 폐기하고 Vue 3 + Vite + Pinia + Tailwind v4 스택으로 재구축하는 M15의 기반(blocker)을 구축한다. 프론트엔드 (frontend/) - Vite + Vue 3(JS) 스캐폴딩: vite.config(outDir→static, emptyOutDir, /api 프록시, @ alias), jsconfig, index.html, 레코드판 favicon - Tailwind v4 CSS-first @theme 디자인 토큰(바이닐 블랙/크림/골드/러스트) + Pretendard - 공통 인프라: axios client(Bearer 자동·Idempotency-Key·401 refresh 1회 인플라이트 직렬화·ProblemDetail→ApiError), Pinia auth/ui 스토어(JWT 디코드·localStorage) - 기능 골격: TheHeader/TheFooter/SearchBar/ToastHost·base 컴포넌트·HomeView·NotFound + History 라우터 백엔드 - SpaRoutes(SPA 라우트 단일 소스) + SpaForwardConfig(화이트리스트 forward:/index.html) - SecurityConfig: SPA 라우트 GET permitAll 추가(API 정책 불변), favicon.svg 공개 - build.gradle.kts: node-gradle download=true(CI 무변경) + frontendBuild(증분 캐싱) + processResources 의존 + clean 정리 + -PskipFrontend 가드 - .gitignore: 빌드 산출물(static/)·node_modules 제외, M14 정적파일 제거 코드 리뷰 반영 - -PskipFrontend 값 없이도 스킵(빈 문자열 가드), 스킵 시 placeholder index.html 로 통합 테스트 404 방지 - axios 응답 빈 본문 null 정규화(M14 fetch 래퍼 동작 보존) - 401 재시도 시 Idempotency-Key 재발급 방지(키 1회 고정) - crypto.randomUUID secure-context 폴백(lib/uuid) - BaseInput type=number 숫자 emit, formatWon 공용 추출(lib/format) - node 버전 단일 소스(.node-version) Closes #113
|
Warning Review limit reached
More reviews will be available in 43 minutes and 21 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
WalkthroughVue 3 및 Vite 기반 SPA를 Gradle과 통합하고, API 클라이언트·Pinia 상태 관리·Tailwind 스타일링을 구현하며 Spring SPA 라우팅을 설정합니다. 기존 바닐라 JS 프론트를 완전히 제거하고 통합 테스트를 업데이트합니다. ChangesVue 3 + Vite SPA 기반으로 마이그레이션
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/main/java/com/groove/auth/security/SecurityConfig.java (1)
124-125: ⚡ Quick winSecurityConfig: SpaRoutes.PATTERNS GET permitAll은 현재 코드에서 bare GET 컨트롤러와 충돌하지 않음
SecurityConfig의requestMatchers(HttpMethod.GET, SpaRoutes.PATTERNS).permitAll()대상(/login,/signup,/me/**,/albums/**,/orders/**,/coupons/**,/admin등)은 현재 코드에서 해당 bare 경로의 GET 핸들러 매핑이 발견되지 않았고,/login,/signup은 POST만 존재합니다.- 다만 “모든 컨트롤러는 /api/v1 prefix” 불변식이 강제되진 않으므로, 해당 bare 경로에 GET 컨트롤러가 추가되는 회귀 방지는 테스트/검증으로 가드하는 편이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/groove/auth/security/SecurityConfig.java` around lines 124 - 125, The current SecurityConfig uses requestMatchers(HttpMethod.GET, SpaRoutes.PATTERNS).permitAll() which unintentionally permits bare GET paths (e.g., /login, /signup, /me/**, /albums/**, /orders/**, /coupons/**, /admin) that currently have no GET handlers but could be added later; tighten this by changing SecurityConfig to only permit the explicit SPA shell/static asset paths (i.e., replace SpaRoutes.PATTERNS with a narrower constant or dedicated SpaShell patterns) or alternatively add a regression test that scans RequestMappingHandlerMapping for any GET mappings matching SpaRoutes.PATTERNS and fails the build if such bare GET controllers exist (reference SecurityConfig and SpaRoutes.PATTERNS when implementing the change).frontend/src/assets/main.css (1)
24-28: ⚡ Quick winStylelint
value-keyword-case회피: 폰트명 인용 처리.
Pretendard(따옴표 없는 토큰)가value-keyword-case로 잘못 플래그됩니다. 소문자화는 폰트명을 깨뜨리므로 잘못된 수정이고, 폰트명을 따옴표로 감싸면 키워드로 인식되지 않아 린트 에러가 사라집니다.♻️ 제안 수정
--font-sans: - 'Pretendard Variable', Pretendard, ui-sans-serif, system-ui, -apple-system, + 'Pretendard Variable', 'Pretendard', ui-sans-serif, system-ui, -apple-system, 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif; --font-display: - 'Pretendard Variable', Pretendard, ui-sans-serif, system-ui, sans-serif; + 'Pretendard Variable', 'Pretendard', ui-sans-serif, system-ui, sans-serif;참고: line 8의
@theme"unknown at-rule" 경고는 stylelint가 SCSS 규칙으로 동작해 Tailwind v4 디렉티브를 모르는 데서 오는 오탐입니다. 코드가 아니라 stylelint 설정(Tailwind 인지 구성 또는at-rule-no-unknownignore)에서 처리하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/assets/main.css` around lines 24 - 28, The CSS custom properties --font-sans and --font-display include unquoted font family tokens (e.g., Pretendard) that trigger stylelint's value-keyword-case; wrap any unquoted font names (such as Pretendard) in quotes inside the --font-sans and --font-display values so they are treated as strings (e.g., 'Pretendard') rather than keywords, leaving existing quoted names intact; do not change the order or fallback stacks.frontend/src/components/ToastHost.vue (1)
17-26: ⚡ Quick win에러 토스트의 스크린리더 알림과 키보드 접근성 개선 권장.
모든 토스트에
role="status"(polite)가 적용되어 있어error타입도 즉시 안내되지 않습니다. 또한@click닫기가<div>에 있어 키보드로는 닫을 수 없습니다(자동 닫힘이 있어 차단 수준은 아님). 타입별role(error →alert)을 분기하고, 닫기를 버튼으로 노출하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/ToastHost.vue` around lines 17 - 26, The toast markup currently renders each item with a non-interactive <div> using role="status" and `@click` for dismissal; update the rendering for items in ToastHost.vue so that the ARIA role is chosen per toast type (use role="alert" when t.type === 'error', otherwise role="status") and replace the click-to-dismiss on the container with a dedicated, keyboard-focusable close control (a button that calls ui.dismiss(t.id)); keep the existing visual classes (styleByType[t.type] || styleByType.info) and ensure the close button is accessible (aria-label or visually-hidden text) while leaving automatic dismissal behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/base/BaseInput.vue`:
- Around line 28-29: Replace the Tailwind focus utility used in the BaseInput
component: in BaseInput.vue update the input's class string to use
focus:outline-hidden instead of focus:outline-none (the class block that
currently includes "focus:outline-none focus:ring-2 focus:ring-gold-400" should
be changed to "focus:outline-hidden focus:ring-2 focus:ring-gold-400"); keep the
existing conditional border classes and ring styles intact so the component's
visual focus ring remains while preserving forced-colors/high-contrast
accessibility.
- Around line 11-17: In onInput, when props.type === 'number' guard against
valueAsNumber returning NaN (e.g. for "-", ".", "1e") before emitting; read
const num = e.target.valueAsNumber and if Number.isNaN(num)
emit('update:modelValue', '') (or emit the raw string if you prefer to preserve
intermediate input) else emit the numeric value; this ensures update:modelValue
never receives NaN from HTMLInputElement.valueAsNumber.
In `@frontend/src/views/HomeView.vue`:
- Around line 16-17: catch 블록에서 ApiError의 detail 또는 title이 둘 다 비어있을 경우
error.value가 빈 문자열이 되어 잘못된 UI가 보입니다; 에러 처리 로직(현재의 catch(e) 블록, 참조: error.value 및
ApiError 검사)을 변경해 e가 ApiError인 경우 e.detail || e.title || '앨범을 불러오지 못했습니다.'와 같이
최종 폴백 메시지를 포함하도록 설정하고, ApiError가 아닐 때도 동일한 기본 메시지로 폴백되게 하여 항상 사용자에게 의미 있는 에러 문구가
표시되도록 수정하세요.
---
Nitpick comments:
In `@frontend/src/assets/main.css`:
- Around line 24-28: The CSS custom properties --font-sans and --font-display
include unquoted font family tokens (e.g., Pretendard) that trigger stylelint's
value-keyword-case; wrap any unquoted font names (such as Pretendard) in quotes
inside the --font-sans and --font-display values so they are treated as strings
(e.g., 'Pretendard') rather than keywords, leaving existing quoted names intact;
do not change the order or fallback stacks.
In `@frontend/src/components/ToastHost.vue`:
- Around line 17-26: The toast markup currently renders each item with a
non-interactive <div> using role="status" and `@click` for dismissal; update the
rendering for items in ToastHost.vue so that the ARIA role is chosen per toast
type (use role="alert" when t.type === 'error', otherwise role="status") and
replace the click-to-dismiss on the container with a dedicated,
keyboard-focusable close control (a button that calls ui.dismiss(t.id)); keep
the existing visual classes (styleByType[t.type] || styleByType.info) and ensure
the close button is accessible (aria-label or visually-hidden text) while
leaving automatic dismissal behavior unchanged.
In `@src/main/java/com/groove/auth/security/SecurityConfig.java`:
- Around line 124-125: The current SecurityConfig uses
requestMatchers(HttpMethod.GET, SpaRoutes.PATTERNS).permitAll() which
unintentionally permits bare GET paths (e.g., /login, /signup, /me/**,
/albums/**, /orders/**, /coupons/**, /admin) that currently have no GET handlers
but could be added later; tighten this by changing SecurityConfig to only permit
the explicit SPA shell/static asset paths (i.e., replace SpaRoutes.PATTERNS with
a narrower constant or dedicated SpaShell patterns) or alternatively add a
regression test that scans RequestMappingHandlerMapping for any GET mappings
matching SpaRoutes.PATTERNS and fails the build if such bare GET controllers
exist (reference SecurityConfig and SpaRoutes.PATTERNS when implementing the
change).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6436184a-7ca7-486f-b0f5-bcd13b5a0f55
⛔ Files ignored due to path filters (3)
.gitignoreis excluded by!.gitignorefrontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/public/favicon.svgis excluded by!**/*.svg
📒 Files selected for processing (38)
build.gradle.ktsfrontend/.gitignorefrontend/.node-versionfrontend/index.htmlfrontend/jsconfig.jsonfrontend/package.jsonfrontend/src/App.vuefrontend/src/api/albums.jsfrontend/src/api/auth.jsfrontend/src/api/client.jsfrontend/src/assets/main.cssfrontend/src/components/SearchBar.vuefrontend/src/components/TheFooter.vuefrontend/src/components/TheHeader.vuefrontend/src/components/ToastHost.vuefrontend/src/components/base/BaseButton.vuefrontend/src/components/base/BaseInput.vuefrontend/src/components/base/BaseSpinner.vuefrontend/src/lib/format.jsfrontend/src/lib/problem-detail.jsfrontend/src/lib/uuid.jsfrontend/src/main.jsfrontend/src/router/index.jsfrontend/src/stores/auth.jsfrontend/src/stores/ui.jsfrontend/src/views/HomeView.vuefrontend/src/views/NotFoundView.vuefrontend/vite.config.jssrc/main/java/com/groove/auth/security/SecurityConfig.javasrc/main/java/com/groove/web/SpaForwardConfig.javasrc/main/java/com/groove/web/SpaRoutes.javasrc/main/resources/static/css/app.csssrc/main/resources/static/index.htmlsrc/main/resources/static/js/api.jssrc/main/resources/static/js/app.jssrc/main/resources/static/js/router.jssrc/main/resources/static/js/store.jssrc/test/java/com/groove/auth/security/SecurityConfigIntegrationTest.java
💤 Files with no reviewable changes (6)
- src/main/resources/static/css/app.css
- src/main/resources/static/js/api.js
- src/main/resources/static/js/router.js
- src/main/resources/static/js/store.js
- src/main/resources/static/js/app.js
- src/main/resources/static/index.html
- BaseInput type=number: valueAsNumber 가 NaN("-", ".", "1e")일 때 '' 보정(NaN emit 차단)
- BaseInput·SearchBar: focus:outline-none → focus:outline-hidden (Tailwind v4, forced-colors 접근성)
- HomeView: ApiError 의 detail·title 이 모두 비어도 기본 문구로 폴백
- main.css: 폰트명 Pretendard 인용(stylelint value-keyword-case 회피)
- ToastHost: error 토스트 role=alert(즉시 안내), 키보드 접근 가능한 닫기 버튼 추가
- SecurityConfigIntegrationTest: SpaRoutes.PATTERNS 와 충돌하는 GET 컨트롤러 매핑이 생기면
실패하는 회귀 가드 추가(forward+permitAll 공유 상수가 실데이터 GET 을 덮는 것 방지)
목적
M14 바닐라 데모 프론트를 폐기하고 Vue 3 + Vite + Pinia + Tailwind v4로 재구축하는 M15의 기반(blocker).
frontend/에 프로젝트를 세우고, Gradle이 빌드 산출물을src/main/resources/static/으로 출력해 같은 오리진(8080)에서 서빙한다. History(clean URL) 라우팅을 위한 Spring SPA fallback과 공통 인프라(API 클라이언트·auth 스토어·레이아웃)를 구축한다. 백엔드 REST API 계약(/api/v1/**)은 그대로 재사용.주요 변경
프론트엔드 (
frontend/)vite.config(outDir→static,emptyOutDir,/api프록시,@alias),jsconfig,index.html, 레코드판favicon.svg@theme디자인 토큰(바이닐 블랙/크림/골드/러스트) + Pretendardapi/client.js(axios — Bearer 자동·Idempotency-Key·401 refresh 1회 인플라이트 직렬화·ProblemDetail→ApiError), Piniastores/auth.js(JWT 디코드·localStorage 키 유지)·stores/ui.jsTheHeader/TheFooter/SearchBar/ToastHost+ base 컴포넌트 +HomeView(앨범 목록)·NotFoundView+ History 라우터백엔드
com.groove.web.SpaRoutes(SPA 라우트 단일 소스) +SpaForwardConfig(명시적 화이트리스트 →forward:/index.html,/api·/actuator·/error미포함)SecurityConfig:SpaRoutes.PATTERNSGET permitAll 추가 (API 정책 불변), 정적 패턴에favicon.svgbuild.gradle.kts: node-gradledownload=true(node 자동 다운로드 → CI 무변경) +frontendBuild(증분 캐싱) +processResources.dependsOn+clean정리 +-PskipFrontend가드.gitignore: 빌드 산출물(static/)·node_modules제외, M14 정적파일 제거검증 (DoD)
./gradlew check그린 — 전체 테스트 + jacoco 커버리지 게이트 통과npm run build→static/에index.html+assets/출력(/assets/*·/favicon.svg참조),emptyOutDir로 M14 잔재 제거./gradlew clean frontendBuild→ node 자동 다운로드 + Vite 빌드 통합/cart·/admin→forward:/index.html(history fallback),/api/v1/admin/**비인증 401 유지, 없는/api/**는 SPA로 forward 안 됨(API/SPA 격리)./gradlew clean check -PskipFrontend→ placeholder index.html로 통합 테스트 통과(node/npm 미실행)코드 리뷰 반영 (extra-high recall)
-PskipFrontend값 없이도 스킵(빈 문자열 가드) + 스킵 시 placeholder로 통합 테스트 404 방지null정규화(M14 fetch 래퍼 동작 보존)Idempotency-Key재발급 방지(키 1회 고정)crypto.randomUUIDsecure-context 폴백(lib/uuid)BaseInput type=number숫자 emit,formatWon공용 추출(lib/format).node-version) +SpaRoutes동기화 의무 주석 강화Closes #113
Summary by CodeRabbit
Release Notes
New Features
Chores