chore(fe): UI 스택 메이저 업그레이드 (React 19 / recharts 3 / Tailwind 4) 및 lint 정리 - #48
Merged
Conversation
- react, react-dom ^18.2.0 → ^19.2.8
- @types/react ^19.2.18, @types/react-dom ^19.2.4
소스 변경은 PerformanceMonitor.tsx 두 곳뿐이다. React 19에서 useRef가
인자를 요구하도록 바뀌어 useRef<number>() → useRef<number|undefined>(undefined)
로 수정했다. main.tsx는 이미 createRoot를 쓰고 있어 손댈 것이 없었다.
vite.config.ts의 manualChunks도 함께 수정했다. React 19는 진입점이
react-dom/client 이라 기존 설정('react-dom'만 명시)으로는 react-dom 본체가
vendor 청크에 잡히지 않고 index로 흘러들어갔다. 그 결과 index 청크가
232kB → 573kB(gzip 70 → 171kB)로 불어나 코드 스플리팅이 사실상 무너졌다.
react-dom/client를 명시해 index 218kB / react-vendor 422kB로 복구했다.
검증: test:run 99 passed(기존 실패 4건 동일), lint 74 problems 동일,
type-check 통과, build 성공, dev 서버 200.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
두 가지 breaking change에 대응했다. 1. CartesianGrid가 자체 yAxisId를 갖게 되어 YAxis와 일치시켜야 한다. 타입 오류가 나지 않고 조용히 깨지는 종류다. 커스텀 yAxisId를 쓰는 EquityChart(return)와 OHLCChart(price) 두 곳이 해당되며, 불일치 시 가로 그리드가 눈금에 맞춘 5개가 아니라 경계선 2개로 축소된다. 2. Tooltip의 formatter/labelFormatter 시그니처가 좁혀졌다(14건). 파라미터를 unknown으로 받고 Number()/String()으로 변환해 해결했다. 실제로 넘어오는 값이 숫자/문자열이라 런타임 출력은 동일하다. any나 @ts-ignore는 쓰지 않았다. 제거된 v3 prop(alwaysShow, isFront, animateNewValues, blendStroke 등)은 사용처가 없었다. CustomTooltip은 TooltipProps를 import하지 않고 자체 인터페이스를 쓰고 있어 TooltipContentProps 개명의 영향을 받지 않았다. 차트 렌더 회귀를 잡을 장치가 없어 ChartRendering.test.tsx를 추가했다. 실제 EquityChart/OHLCChart를 렌더해 SVG 구조(그리드 선 개수, 축 개수, 곡선/막대 존재)를 검증한다. 색상·여백·툴팁 포맷·범례는 다루지 않는다. 알려진 동작 변화: v3는 범례 순서가 JSX 선언 순서를 따르는 것을 보장하지 않는다. EquityChart와 OHLCChart의 범례 항목 순서가 v2와 달라진다. 데이터에는 영향이 없고, v2 순서를 강제하려면 v3가 제거한 Legend payload가 필요하다. 검증: test:run 104 passed(신규 5건 포함, 기존 실패 4건 동일), lint 74 problems 동일, type-check 통과, build 성공, chart-vendor 청크 유지(445kB, index 유출 0건), dev 서버 200. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- tailwindcss ^3.3.0 → ^4.3.3, @tailwindcss/postcss 추가 - autoprefixer 제거 (v4가 벤더 프리픽스를 자체 처리) - postcss.config.js 플러그인 교체 - tailwind.config.js 삭제, 설정을 src/index.css로 이관 (components.json의 config 경로도 빈 문자열로 갱신) ## 런타임 테마 주입 보존 이 앱은 useTheme이 root.style.setProperty()로 CSS 변수를 써넣어 테마를 바꾼다. v4의 @theme이 값을 빌드타임에 고정해버리면 테마 전환이 조용히 죽는다. index.css에 이미 있던 @theme inline 블록(v3에서는 무시되던 죽은 코드)이 이 용도에 정확히 맞아 그대로 활용했다. 색상 리터럴은 @theme으로 옮기지 않았다. 빌드된 CSS로 확인: 유틸리티가 var(--background) 등을 참조하는 규칙 105건, --color-background 방출 0건(고정 흔적 없음). ## darkMode: class 복원 v4는 prefers-color-scheme이 기본이라 @custom-variant dark (&:is(.dark *)) 를 추가했다. 빌드 CSS에 .dark\: 셀렉터 14건, prefers-color-scheme 0건. ## 유틸리티 변경 대응 - bg-black bg-opacity-50 → bg-black/50 (LoadingSpinner) bg-opacity-*는 v4에서 제거됐다. 그대로 뒀으면 전체 화면 로딩 오버레이가 반투명이 아니라 불투명 검정이 됐다. - outline-none → outline-hidden (16곳) v3의 outline-none은 outline: 2px solid transparent였으나 v4에서는 outline-style: none이다. 기존 동작은 outline-hidden이다. - backdrop-blur-sm → backdrop-blur-xs (5곳) v4에서 blur-sm이 4px → 8px로 바뀌었다. - flex-shrink-0 → shrink-0 (6곳) - container → app-container (6곳) v4가 container 설정 객체를 제거하고 브레이크포인트별 max-width를 가진 자체 .container를 방출한다. 그대로 두면 768~1023px 구간에서 랜딩 페이지가 768px로 좁아진다. v3 설정(center, padding 2rem, 2xl 1400px)을 재현한 .app-container를 components 레이어에 정의했다. @tailwindcss/upgrade 코드모드는 쓰지 않았다. 이 프로젝트는 --radius-*와 --shadow-*를 직접 정의하므로 코드모드의 일괄 개명(rounded-sm→rounded-xs, shadow-sm→shadow-xs 등)이 오히려 모든 모서리와 그림자를 바꿔놓는다. ## 의도된 시각 변화 v3 설정은 boxShadow를 확장하지 않아 :root의 --shadow-* 토큰이 실제로는 쓰이지 않았다. v4에서 @theme inline이 이를 연결하므로 그림자가 테마 값을 따르게 된다. rounded-xl도 고정 0.75rem에서 calc(var(--radius) + 4px)로 바뀌어 테마에 따라 달라진다. CSS 번들 49.71 → 73.81 kB (gzip 9.34 → 12.35 kB). @Property 등록, @layer theme 기본 토큰, 합성 가능한 box-shadow 체인 등 v4 구조상 오버헤드다. 검증: test:run 104 passed(기존 실패 4건 동일), lint 74 problems 동일, type-check 통과, build 성공, 청크 3종 유지, dev 서버 200 및 CSS 89KB 서빙. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
any를 실제 타입으로 대체하는 방식으로 정리했다. eslint-disable, @ts-ignore, 규칙 완화는 쓰지 않았다. 규칙별 변화: - @typescript-eslint/no-explicit-any 65 → 16 - prefer-const 2 → 0 - react-hooks/exhaustive-deps 7 → 7 (유지) recharts 3가 제공하는 타입(TooltipContentProps, formatter 타입, 범례 클릭 페이로드, textAnchor 유니온)과 앱이 이미 선언해둔 도메인 타입 (src/features/backtest/model/types/)을 활용했다. 남은 23건은 의도적으로 두었다. - exhaustive-deps 7건: 의존성을 강제로 추가하면 런타임 동작이 바뀌거나 무한 렌더 루프가 될 수 있는 훅들이다. 경고가 남더라도 동작이 올바른 쪽을 택했다. 개별 분석 없이 일괄 수정할 성질이 아니다. - no-explicit-any 16건: 타입을 정직하게 좁히려면 별도 리팩터링이 필요한 경계 지점들이다. npm run lint은 --max-warnings 0 이므로 여전히 실패한다. 이 커밋의 목표는 lint 통과가 아니라 타입 정확도 개선이다. 검증: test:run 104 passed(기존 실패 4건 동일), type-check 오류 0, build 성공(청크 및 CSS 크기 동일), dev 서버 200. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
벤치마크/비중 차트와 차트 데이터 훅의 any를 실제 타입으로 대체했다. - BenchmarkIndexChart, BenchmarkReturnsChart, BenchmarkSection - WeightHistoryChart - hooks/charts/useChartData @typescript-eslint/no-explicit-any 16 → 2. eslint-disable, @ts-ignore, 규칙 완화는 쓰지 않았다. 남은 9건: - react-hooks/exhaustive-deps 7건 — 의존성을 강제 추가하면 런타임 동작이 바뀌거나 무한 렌더 루프가 될 수 있는 훅들이라 그대로 둔다. - no-explicit-any 2건 — 좁히려면 별도 리팩터링이 필요한 경계 지점이다. 검증: test:run 104 passed(기존 실패 4건 동일), type-check 오류 0, build 성공(청크 및 CSS 크기 동일). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
FE UI 스택을 React 19 / Recharts 3 / Tailwind CSS 4로 메이저 업그레이드하고, 누적된 타입·lint 위반(특히 any)을 정리해 마이그레이션 안정성과 타입 정확도를 높이는 PR입니다. Recharts v3의 “조용히 깨지는” 그리드 정렬 회귀를 잡기 위한 SVG 구조 기반 회귀 테스트도 추가됩니다.
Changes:
- React 19 전환에 맞춰 Vite 청크 분리(
manualChunks)와 일부 디버그/타이핑 코드를 정리 - Recharts v3 마이그레이션(특히
CartesianGrid의yAxisIdbreaking change 대응) 및 차트/툴팁 타입 개선 - Tailwind CSS v4 전환(
tailwind.config.js제거,index.css기반 @source/@theme 구성) + UI 컴포넌트 포커스/레이아웃 유틸 정리
Reviewed changes
Copilot reviewed 50 out of 52 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| backtest_fe/vite.config.ts | React 19 진입점(react-dom/client) 포함하도록 vendor chunk 분리 보완 |
| backtest_fe/tailwind.config.js | Tailwind v4 전환으로 설정 파일 제거 |
| backtest_fe/src/vite-env.d.ts | performance.memory 등 비표준 API 타입 보강 및 define 주입 글로벌 타입 유지 |
| backtest_fe/src/shared/utils/dataSampling.ts | 샘플링 유틸 제네릭 타입에서 any 제거/축소 |
| backtest_fe/src/shared/ui/textarea.tsx | Tailwind v4 포커스 outline 유틸 변경 반영 |
| backtest_fe/src/shared/ui/tabs.tsx | Tailwind v4 포커스 outline 유틸 변경 반영 |
| backtest_fe/src/shared/ui/select.tsx | Tailwind v4 outline 유틸 및 일부 outline-* 정리 |
| backtest_fe/src/shared/ui/input.tsx | Tailwind v4 포커스 outline 유틸 변경 반영 |
| backtest_fe/src/shared/ui/dropdown-menu.tsx | Tailwind v4 outline 유틸 변경 반영 |
| backtest_fe/src/shared/ui/dialog.tsx | backdrop blur 스케일 변경 및 close 버튼 outline 유틸 변경 |
| backtest_fe/src/shared/ui/button.tsx | 버튼 기본 스타일의 outline 유틸 변경 |
| backtest_fe/src/shared/ui/badge.tsx | 배지 outline 유틸 변경 |
| backtest_fe/src/shared/components/loading/LoadingSpinner.tsx | bg-opacity-* 제거 대응(bg-black/50) |
| backtest_fe/src/shared/components/layout/ThemeSelector.tsx | theme: any 제거 및 포커스 outline 유틸 변경 |
| backtest_fe/src/shared/components/layout/ErrorBoundary.tsx | Tailwind v4 .container 변화 대응으로 app-container 사용 |
| backtest_fe/src/shared/components/feedback/ErrorMessage.tsx | flex-shrink-0 → shrink-0, outline 유틸 변경 |
| backtest_fe/src/shared/components/debug/PerformanceMonitor.tsx | React 19 useRef 초기값 요구 대응 + performance.memory 안전 접근 |
| backtest_fe/src/shared/api/base.ts | import.meta의 any 캐스팅 제거, 타입 기반 접근으로 정리 |
| backtest_fe/src/pages/PortfolioPage.tsx | backdrop blur 스케일 변경 반영 |
| backtest_fe/src/pages/landing/WhatIsBacktestSection.tsx | container → app-container로 레이아웃 유지 |
| backtest_fe/src/pages/landing/KeyFeaturesSection.tsx | container → app-container, shrink 유틸 정리 |
| backtest_fe/src/pages/landing/HowToUseSection.tsx | container → app-container로 레이아웃 유지 |
| backtest_fe/src/pages/landing/HeroSection.tsx | container → app-container로 레이아웃 유지 |
| backtest_fe/src/pages/landing/CTASection.tsx | container → app-container로 레이아웃 유지 |
| backtest_fe/src/lib/tests/chartUtils.test.ts | 차트 유틸 테스트에서 any 제거 및 입력 정규화 타입 도입 |
| backtest_fe/src/index.css | Tailwind v4 @import/@source/@custom-variant/@theme 및 .app-container 정의 |
| backtest_fe/src/features/backtest/model/types/index.ts | 새 타입(ExchangeRateStats, BenchmarkSeriesPoint) export 추가 |
| backtest_fe/src/features/backtest/model/types/backtest-result-types.ts | 백엔드 계약에 맞춘 결과 타입 확장/정밀화(indicators, exchange_stats 등) |
| backtest_fe/src/features/backtest/hooks/charts/useChartData.ts | exchangeStats를 unknown → 구체 타입으로 정리 |
| backtest_fe/src/features/backtest/components/volatility/VolatilityTable.tsx | 포커스 outline 유틸 변경 반영 |
| backtest_fe/src/features/backtest/components/TradesChart.tsx | 로컬 Trade 타입 제거, 공용 TradeMarker 타입 사용 |
| backtest_fe/src/features/backtest/components/StrategyHelpModal.tsx | flex-shrink-0 → shrink-0 정리 |
| backtest_fe/src/features/backtest/components/StockPriceChart.tsx | 차트 포인트 타입 도입 및 tooltip formatter 시그니처 정리 |
| backtest_fe/src/features/backtest/components/results/WeightHistoryChart.tsx | recharts v3 tooltip formatter 시그니처(unknown) 대응 |
| backtest_fe/src/features/backtest/components/results/WarningBanner.tsx | flex-shrink-0 → shrink-0 정리 |
| backtest_fe/src/features/backtest/components/results/ChartsSection/SupplementaryCharts.tsx | 다수 any 제거, 환율/뉴스/리밸런싱 타입 명확화 |
| backtest_fe/src/features/backtest/components/results/ChartsSection/SingleStockCharts.tsx | 차트 props any 캐스팅 제거 및 타입 연결 |
| backtest_fe/src/features/backtest/components/results/ChartsSection/PortfolioCharts.tsx | tooltip formatter 시그니처 정리 및 타입 강화 |
| backtest_fe/src/features/backtest/components/results/ChartsSection/BenchmarkSection.tsx | 벤치마크 데이터 타입을 명확화 |
| backtest_fe/src/features/backtest/components/results/BenchmarkReturnsChart.tsx | recharts v3 tooltip formatter 시그니처(unknown) 대응 |
| backtest_fe/src/features/backtest/components/results/BenchmarkIndexChart.tsx | recharts v3 tooltip formatter 시그니처(unknown) 대응 및 const 정리 |
| backtest_fe/src/features/backtest/components/PortfolioBacktestForm.tsx | 422 에러 파싱에서 any 제거(unknown 기반) |
| backtest_fe/src/features/backtest/components/OHLCChart.tsx | recharts v3 CartesianGrid yAxisId breaking change 대응 |
| backtest_fe/src/features/backtest/components/ExchangeRateChart.tsx | tooltip formatter 타입 정리 |
| backtest_fe/src/features/backtest/components/EquityChart.tsx | recharts v3 CartesianGrid yAxisId 대응 및 textAnchor 타입 보강 |
| backtest_fe/src/features/backtest/components/BacktestResults.tsx | backdrop blur 스케일 변경 반영 |
| backtest_fe/src/features/backtest/components/tests/ChartRendering.test.tsx | recharts v3 그리드 회귀 방지용 SVG 구조 기반 렌더링 테스트 추가 |
| backtest_fe/src/features/backtest/components/tests/BacktestForm.test.tsx | 폼 submit mock의 데이터 타입을 any → 명시 타입으로 정리 |
| backtest_fe/postcss.config.js | Tailwind v4용 @tailwindcss/postcss 플러그인으로 전환 |
| backtest_fe/package.json | React/Recharts/Tailwind v4 업그레이드 및 devDependency 정리 |
| backtest_fe/package-lock.json | 업그레이드에 따른 lockfile 갱신 |
| backtest_fe/components.json | shadcn 설정에서 tailwind config 경로 제거 반영 |
Files not reviewed (1)
- backtest_fe/package-lock.json: Generated file
Comment on lines
+85
to
+90
| afterAll(() => { | ||
| global.ResizeObserver = ORIGINAL_RO; | ||
| if (ORIGINAL_RECT) { | ||
| Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', ORIGINAL_RECT); | ||
| } | ||
| }); |
증상: dev(localhost:5173)에서 백테스트 실행 시 404.
원인: axios 클라이언트가 baseURL로 VITE_API_BASE_URL(=/api)을 쓰는데,
서비스 레이어(backtestService.ts, backtestApi.ts)는 전체 경로
'/api/v1/backtest'를 그대로 넘긴다. 두 값이 겹쳐 실제 요청이
'/api/api/v1/backtest'가 되고, vite 프록시는 '/api/v1/backtest'만
매칭하므로 BE까지 가지 못하고 dev 서버가 404를 반환했다.
BE 로그에 브라우저發 404가 전혀 남지 않던 이유다.
base.ts의 주석("빈 문자열 반환: backtestService.ts에서 전체 경로를 사용")이
이미 이 계약을 명시하고 있었으나, compose.dev.yaml이 기본값으로 /api를
주입해 계약을 깨고 있었다. 운영은 Dockerfile이 이 값을 빌드 인자로 넘기지
않아 빈 값이 되므로 영향이 없었고, 그래서 dev에서만 재현됐다.
수정:
1. compose.dev.yaml 기본값을 빈 값으로 변경. ':-'는 빈 문자열도 기본값으로
대체하므로 '-'를 사용해 빈 값이 유지되도록 했다.
2. .env.example도 빈 값으로 바로잡고 이유를 주석으로 남겼다.
3. client.ts에 요청 경로가 이미 baseURL 경로로 시작하면 중복을 제거하는
인터셉터를 추가했다. 설정이 잘못되더라도 앱이 동작하도록 방어한다.
회귀 테스트 6건 추가(src/shared/api/__tests__/client.test.ts).
수정 전 상태에서 실패하는 것을 확인한 뒤 수정했다.
getUri()는 인터셉터를 거치지 않으므로 어댑터를 가로채 실제 요청 URL을 검증한다.
검증: POST :5173/api/v1/backtest 200, vite가 변환한 모듈에서
VITE_API_BASE_URL="" 확인, test:run 110 passed(기존 실패 4건 동일),
type-check 오류 0, lint 9건 유지, build 성공.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
백엔드는 통합 엔드포인트 POST /api/v1/backtest 하나만 제공하고 주가/환율/뉴스/벤치마크 데이터를 모두 그 응답에 담아 보낸다. 과거 엔드포인트를 분리해 쓰던 시절의 FE 코드가 남아 있었고, 호출하면 전부 404가 나는 상태였다. 실제 화면은 SupplementaryCharts가 응답 props로 그리고 있어 이 코드들은 렌더되지 않는 죽은 코드였다. 삭제: - api/backtestApi.ts (전체) — 아래 훅들만 사용하던 파일 - hooks/useStockData.ts, useExchangeRate.ts, useVolatilityNews.ts - components/ExchangeRateChart.tsx, StockVolatilityNews.tsx - LazyChartComponents의 LazyExchangeRateChart, LazyStockVolatilityNews - BacktestService의 getStrategies, getStrategy, searchNews, getExchangeRate, getVolatilityData, getSystemInfo (executeBacktest만 남는다) - 위에 딸린 타입: NewsResponse, VolatilityData, ExchangeRateData, SystemInfo, StockVolatilityNewsProps, ApiEndpoint union (ApiEndpoint는 존재하지 않는 엔드포인트 목록이라 오히려 오해를 부른다) searchNews 통합 테스트 1건도 함께 제거했다. MSW로 모킹하고 있어 백엔드와 무관하게 통과하던, 죽은 메서드를 살려두던 유일한 참조였다. 삭제 전 각 심볼의 참조를 전수 확인해 사슬이 닫혀 있음을 검증했다. 부수 효과로 lint가 9 → 5로 줄었다(exhaustive-deps 7 → 3). 삭제된 훅들이 갖고 있던 경고다. 검증: test:run 109 passed(searchNews 테스트 1건 제거분 반영, 기존 실패 4건 동일), type-check 오류 0, build 성공, POST /api/v1/backtest 200, dev 서버 200. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 중복되거나 오래된 항목 제거 (Stack, URLs, 네이티브 실행 커맨드, 하드코딩된 테스트 개수) - Sub-Agent 목록을 실제 사용 가능한 것으로 교체 (Explore / Plan / general-purpose) - DB 항목은 schema.sql 참조로 단순화 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyj0503
added a commit
that referenced
this pull request
Aug 2, 2026
#47·#48·#49로 스택과 검증 체계가 바뀌었는데 문서가 따라가지 못했다. 실측한 값으로만 갱신했고, 문서에 적은 명령·경로·수치는 모두 실행해 존재를 확인했다. ## 사실과 달랐던 것 - backtest_fe/README.md: React 18 표기(실제 19), 테스트 13파일/98건 (실제 16/113), 커버리지 17.13%(실제 21.81%), 존재하지 않는 src/components/ 디렉터리 설명, shared 파일 수 불일치, 끊긴 링크 4개(TEST.md 등 모두 부재) - backtest_fe/docs/testing/execution.md: 설정 파일을 vite.config.ts로 안내(실제 vitest.config.ts), 환경을 jsdom으로 안내(실제 happy-dom) - backtest_be_fast/tests/README.md: 총 68건(실제 141건) - UNIT_TEST_QUICK_REFERENCE.md: 파일별 개수 3건 불일치, 하드코딩된 타인 절대경로(/home/coontec/...), venv 기반 실행 안내, 실제와 다른 CI 예시(GitHub Actions/venv) - TEST_COVERAGE_SUMMARY.md: 파일별 개수 3건 불일치, venv 실행 안내 (합계 59건은 이 문서가 다루는 4개 모듈 기준으로 지금도 정확해 유지) - README.md(루트): backtest_fe/__tests__/ 구조(이제 src 안으로 이동), 스택 버전 미표기 ## 새로 담은 것 - CI Quality Gate의 존재와 재현 방법(docker build --target test) - 게이트가 배포는 막지만 병합은 막지 않는다는 점(브랜치 보호 미사용) - type-check와 type-check:test의 분리 이유 - lint 경고 상한 3이 래칫이라는 점과 목표 - Tailwind 4 제약(설정이 index.css, @theme에 색상 리터럴 금지, .app-container) - vitest isolate:false 금지 이유 - FE build의 NODE_ENV=production 고정 이유 - VITE_API_BASE_URL을 비워야 하는 계약 - build:analyze가 현재 실질적으로 build와 동일하다는 주의 CLAUDE.md와 .github/copilot-instructions.md에는 위 제약들을 에이전트가 반복해서 밟지 않도록 명시했다. BE 문서 2건은 편집 과정에서 CRLF가 LF로 바뀌어 원래 개행으로 되돌렸다. 코드 변경은 없다(마크다운만). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
개요
PR #47(보안·의존성 정리)에 이어, 남아 있던 UI 스택 메이저 업그레이드를 진행했습니다.
React 18→19, recharts 2→3, Tailwind CSS 3→4, lint 위반 정리, 그리고 작업 중 발견한 개발 환경 404 버그 수정과 죽은 코드 제거입니다. 모든 검증은
compose.dev.yaml컨테이너에서 실행했습니다.이 PR은 자동 검증만으로 안전을 보장할 수 없습니다. 차트 렌더링과 CSS는 단위 테스트·타입체크·빌드가 전혀 보지 못하는 영역이고, 이 저장소에는 Playwright e2e가 실행 가능한 환경이 없습니다(컨테이너에 브라우저 없음).
아래 항목을 직접 확인해 주세요. 단계별로 커밋이 분리되어 있어 문제가 보이면 해당 커밋만 되돌릴 수 있습니다.
의도된 시각 변화 — 달라 보이는 것이 정상입니다
그림자 전반 (카드, 버튼, 모달, 호버, 토스트)
v3 설정은
boxShadow를 확장하지 않아:root의--shadow-*토큰이 실제로는 한 번도 사용되지 않았습니다. v4의@theme inline이 이를 연결하면서 이제 그림자가 활성 테마 값을 따릅니다. claymorphism이면 얇은 hairline 대신 두툼한 clay 그림자가 나옵니다. 테마 시스템이 원래 의도한 동작이지만 실질적인 리스타일링입니다.rounded-xl— 고정0.75rem→calc(var(--radius) + 4px)v3 설정이
sm/md/lg만 재정의했기 때문입니다. 이제 테마에 따라 달라집니다 (claymorphism 24px, amber-minimal 10px). Button 포함 13곳.차트 범례 순서 (recharts v3)
v3는 범례 순서가 JSX 선언 순서를 따르는 것을 보장하지 않습니다.
["드로우다운", "수익률"]— v2와 역순["SMA20", "거래량", "종가"]— 지표가 앞으로데이터에는 영향이 없습니다. v2 순서를 강제하려면 v3가 제거한
Legend payload가 필요해 방법이 없습니다. 순서가 문제라면 JSX 선언 순서를 바꾸는 방식으로 대응 가능합니다.회귀 확인 항목
container→app-container교체. 1400px까지 full-bleed, 이후 고정divide-y,border-t/b, Card)outline-none→outline-hidden₩1234.56, 벤치마크 소수 2자리 + 한글 계열명, 수익률%커밋별 내용
chore(fe)React 18 → 19소스 변경은
PerformanceMonitor.tsx두 줄뿐입니다 (useRef가 인자를 요구하도록 변경).main.tsx는 이미createRoot를 사용 중이었습니다.추가로
vite.config.ts의manualChunks를 수정했습니다. React 19는 진입점이react-dom/client라 기존 설정('react-dom'만 명시)으로는 react-dom 본체가 vendor 청크에 잡히지 않고 index로 흘러들어갔습니다. index 청크가 232kB → 573kB(gzip 70 → 171kB)로 불어나 코드 스플리팅이 무너진 상태였고, 테스트·타입체크·빌드 모두 통과하는 상태였습니다.react-dom/client를 명시해 index 218kB / react-vendor 422kB로 복구했습니다.chore(fe)recharts 2.15.4 → 3.10.1CartesianGrid가 자체yAxisId를 갖게 됨 — 타입 오류 없이 조용히 깨지는 변경입니다. 불일치 시 가로 그리드가 눈금 정렬 5개 → 경계선 2개로 축소됩니다. 커스텀yAxisId를 쓰는 EquityChart(return), OHLCChart(price) 두 곳이 해당됩니다.formatter/labelFormatter시그니처 축소 (14건) — 파라미터를unknown으로 받고Number()/String()으로 변환. 런타임 출력은 동일합니다.차트 렌더 회귀를 잡을 장치가 없어
ChartRendering.test.tsx를 추가했습니다. 실제 EquityChart/OHLCChart를 렌더해 SVG 구조(그리드 선 개수, 축 개수, 곡선/막대 존재)를 검증합니다. 색상·여백·툴팁 포맷·범례는 다루지 않습니다.chore(fe)Tailwind CSS 3 → 4가장 큰 리스크는 런타임 테마 주입이었습니다. 이 앱은
useTheme이root.style.setProperty()로 CSS 변수를 써넣어 테마를 바꾸는데, v4의@theme이 값을 빌드타임에 고정하면 테마 전환이 조용히 죽습니다.index.css에 이미 있던@theme inline블록(v3에서는 무시되던 죽은 코드)이 이 용도에 정확히 맞아 활용했습니다.빌드된 CSS로 검증했습니다:
조용히 깨졌을 변경 두 가지를 잡았습니다:
bg-opacity-*제거 → 전체 화면 로딩 오버레이가 불투명 검정이 될 뻔했습니다container설정 객체를 제거하고 브레이크포인트별 max-width를 가진 자체.container를 방출 → 랜딩 페이지가 768~1023px 구간에서 768px로 좁아질 뻔했습니다@tailwindcss/upgrade코드모드는 사용하지 않았습니다. 이 프로젝트는--radius-*/--shadow-*를 직접 정의하므로 코드모드의 일괄 개명(rounded-sm→rounded-xs,shadow-sm→shadow-xs)이 오히려 모든 모서리와 그림자를 바꿔놓습니다.CSS 번들 49.71 → 73.81 kB (gzip 9.34 → 12.35 kB).
@property등록,@layer theme기본 토큰, 합성형 box-shadow 체인 등 v4 구조상 오버헤드입니다.refactor(fe)lint 위반 정리 (74 → 9, 커밋 2개)any를 실제 타입으로 대체했습니다.eslint-disable,@ts-ignore, 규칙 완화는 사용하지 않았습니다 (변경분 grep으로 확인).@typescript-eslint/no-explicit-anyprefer-constreact-hooks/exhaustive-deps남은 9건은 의도적으로 두었습니다.
exhaustive-deps7건은 의존성을 강제 추가하면 런타임 동작이 바뀌거나 무한 렌더 루프가 될 수 있는 훅들이라, 경고가 남더라도 동작이 올바른 쪽을 택했습니다.no-explicit-any2건은 정직하게 좁히려면 별도 리팩터링이 필요한 경계 지점입니다.fix(fe)개발 환경 API 경로 이중 접두사 404 수정작업 중 dev에서 백테스트 실행이 404가 나는 것을 발견해 원인을 추적했습니다.
vite 프록시는
/api/v1/backtest만 매칭하므로 BE까지 가지도 못하고 dev 서버가 404를 냈습니다(BE 로그에 브라우저發 404가 전혀 없던 이유).base.ts의 주석이 이미 "전체 경로를 쓰므로 base는 빈 문자열"이라는 계약을 명시하고 있었으나,compose.dev.yaml이 기본값/api를 주입해 계약을 깨고 있었습니다.운영은 영향 없었습니다 —
Dockerfile이 이 값을 빌드 인자로 넘기지 않아 자연히 빈 값이 되고, nginx도/api/ → backtest-be:8000/api/로 그대로 넘깁니다. dev 전용 문제였습니다.조치는 설정과 코드 양쪽에 했습니다. compose 기본값을 빈 값으로(
:-→-, 빈 문자열이 유지되도록),.env.example도 정정, 그리고client.ts에 경로 중복을 덜어내는 인터셉터를 추가해 설정이 잘못돼도 동작하도록 방어했습니다. 회귀 테스트 6건을 추가했고, 수정 전 상태에서 실패하는 것을 확인한 뒤 고쳤습니다.refactor(fe)존재하지 않는 API를 호출하던 죽은 코드 제거백엔드는 통합 엔드포인트 하나만 제공하고 주가/환율/뉴스/벤치마크를 모두 그 응답에 담아 보냅니다. 엔드포인트가 분리돼 있던 시절의 FE 코드가 남아 있었고, 호출하면 전부 404가 나는 상태였습니다. 실제 화면은
SupplementaryCharts가 응답 props로 그리고 있어 렌더되지 않는 죽은 코드였습니다.api/backtestApi.ts(전체), 훅 3개, 컴포넌트 2개, lazy export 2개,BacktestService메서드 6개(executeBacktest만 남김), 관련 타입 6종을 제거했습니다. 특히ApiEndpoint는 존재하지 않는 엔드포인트 6개를 나열한 유니온이라 남겨두면 오해를 부릅니다.searchNews는 자기 테스트 하나가 유일하게 살려두고 있었습니다(MSW 모킹이라 BE와 무관하게 통과). 죽은 코드를 검증하는 테스트라 함께 제거했습니다.부수 효과로 lint가 9 → 5로 줄었습니다(
exhaustive-deps7 → 3). 앞서 "무한 루프 위험으로 남긴다"고 한 경고 중 4건이 삭제된 훅들의 것이었습니다.검증
npm run test:runnpm run type-checknpm run lintnpm run buildPOST /api/v1/backtest알려진 이슈
portfolioCalculations.test.ts3건,ThemeSelector.test.tsx1건입니다. 이 PR 전체에서 순증 0입니다.npm run lint는 여전히 실패합니다 (--max-warnings 0이므로 5건이면 실패). 목표는 lint 통과가 아니라 타입 정확도 개선이었습니다. 남은 5건은no-explicit-any2 +exhaustive-deps3으로, 의존성을 강제 추가하면 런타임 동작이 바뀔 수 있는 훅들입니다.🤖 Generated with Claude Code