docs: Plan 1 (Week 1 엔진 마이그레이션) 구현 계획 작성 (JSA-2)

Granite + Apps-in-Toss + TDS 종속성을 제거하고 Vite + 순수 React로
전환하는 16개 task 구현 계획. 각 task는 2~5분 단위 step으로 분해되어
TDD 회귀 테스트, git 커밋 리듬, 명시적 코드 블록을 모두 포함.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 08:18:34 +09:00
parent 0106f9c597
commit 664fe881dd

View File

@@ -0,0 +1,881 @@
# Week 1 — Engine Migration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Granite + Apps-in-Toss + TDS 종속성을 완전히 제거하고 Vite + 순수 React 위에서 모든 기존 게임 기능이 회귀 없이 동작하는 빌드를 만든다.
**Architecture:** 게임 로직(`useGameStore`, JSON 데이터, 컴포넌트)은 변경하지 않는다. 빌드 시스템(Granite → Vite), 진입점(`_app.tsx` + `pages/index.tsx``main.tsx` + `App.tsx`), 외부 의존성(TDS 색상 → 로컬 shim, Toss Analytics → console adapter)만 교체한다. `src/platform/` 디렉토리를 도입하여 향후 모든 vendor 격리의 진입점으로 삼는다.
**Tech Stack:** Vite 5.x, @vitejs/plugin-react, React 18, TypeScript 5.x, @emotion/react, zustand (모두 유지)
**Spec reference:** `docs/superpowers/specs/2026-04-27-web-engine-pivot-design.md` 섹션 1, 2, 5.3, 5.4, 타임라인 Week 1
**TDD note:** 본 plan은 신규 비즈니스 로직이 거의 없는 마이그레이션 plan이다. 검증은 (1) TypeScript 컴파일, (2) Vite dev/build 통과, (3) 수동 회귀 체크리스트로 한다. 단위 테스트 프레임워크(Vitest) 도입은 Plan 3(광고 어댑터 — 신규 로직 발생)에서 진행한다.
---
## File Structure
| 작업 | 파일 |
|------|------|
| **신규** | `index.html`, `vite.config.ts`, `tsconfig.node.json`, `src/main.tsx`, `src/App.tsx`, `src/config/env.ts`, `src/styles/adaptive.ts`, `src/platform/analytics/types.ts`, `src/platform/analytics/consoleAdapter.ts`, `src/platform/analytics/index.ts` |
| **수정** | `package.json`, `tsconfig.json`, `.gitignore`, `src/styles/gameColors.ts`, `src/components/AchievementToast.tsx`, `src/components/OfflineRewardModal.tsx`, `src/components/PrestigeModal.tsx`, `src/components/screens/AchievementsScreen.tsx`, `src/components/screens/ElementsScreen.tsx`, `src/components/screens/SettingsScreen.tsx`, `src/components/screens/ShopScreen.tsx` |
| **삭제** | `babel.config.js`, `granite.config.ts`, `require.context.ts`, `global.d.ts`, `src/_app.tsx`, `pages/index.tsx`, `pages/` (디렉토리), `src/analytics.ts`, `.swc/`, `dist/` |
| **변경 없음** | `src/store/useGameStore.ts`, `src/data/*.json`, `src/hooks/*.ts`, `src/components/BottomTabBar.tsx`, `src/components/CharacterSprite.tsx`, `src/components/FloatingOverlay.tsx`, `src/components/screens/EvolutionScreen.tsx`, `src/components/screens/FusionScreen.tsx`, `src/components/tutorial/*.tsx` |
---
## Task 1: 백업 git 태그 + 작업 브랜치 생성
**Files:** 없음 (git 작업)
- [ ] **Step 1: 현재 master에 백업 태그 생성**
```bash
git tag -a pre-vite-migration -m "Last Granite/Toss-based build before Vite migration"
```
- [ ] **Step 2: 작업 브랜치 생성**
```bash
git switch -c feat/vite-migration
```
- [ ] **Step 3: 태그 + 브랜치 확인**
Run: `git tag -l pre-vite-migration && git branch --show-current`
Expected: `pre-vite-migration` (태그 출력) + `feat/vite-migration` (브랜치 출력)
---
## Task 2: package.json 의존성 교체
**Files:**
- Modify: `package.json` (전체)
- [ ] **Step 1: package.json 새 내용으로 교체**
```json
{
"name": "archetype-firstspark",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"zustand": "^5.0.12"
},
"devDependencies": {
"@types/node": "^22.7.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.6.3",
"vite": "^5.4.11"
}
}
```
- [ ] **Step 2: lockfile 및 node_modules 정리**
```bash
rm -rf node_modules package-lock.json
```
- [ ] **Step 3: 새 의존성 설치**
```bash
npm install
```
Expected: 설치 완료, `npm warn` 정도는 OK, 에러 없음
- [ ] **Step 4: 커밋**
```bash
git add package.json package-lock.json
git commit -m "chore: swap deps to Vite + React 18, remove Granite/Toss/RN"
```
---
## Task 3: Granite/Toss 빌드 산출물 + config 파일 삭제
**Files:**
- Delete: `babel.config.js`, `granite.config.ts`, `require.context.ts`, `global.d.ts`, `src/_app.tsx`, `pages/index.tsx`, `.swc/`, `dist/`
- [ ] **Step 1: Granite·RN 전용 config 파일 삭제**
```bash
rm -f babel.config.js granite.config.ts require.context.ts global.d.ts
```
- [ ] **Step 2: Toss/Granite 진입점 파일 삭제**
```bash
rm -f src/_app.tsx
rm -rf pages
```
- [ ] **Step 3: 빌드 캐시 디렉토리 삭제**
```bash
rm -rf .swc dist
```
- [ ] **Step 4: 커밋 (이 시점에 dev 부트는 아직 안 됨, 다음 task에서 복구)**
```bash
git add -A
git commit -m "chore: remove Granite/Toss config and entry files"
```
---
## Task 4: .gitignore 업데이트
**Files:**
- Modify: `.gitignore`
- [ ] **Step 1: 현재 .gitignore 확인**
Run: `cat .gitignore`
주의: 현재 내용 그대로 두고, 아래 추가만 함.
- [ ] **Step 2: Vite 관련 항목 추가 (이미 있다면 중복 추가하지 않음)**
`.gitignore`에 다음 줄이 없으면 추가:
```
# Vite
.vite/
dist/
*.local
# IDE
.vscode/
.idea/
```
- [ ] **Step 3: 커밋**
```bash
git add .gitignore
git commit -m "chore: ignore Vite build outputs and IDE caches"
```
---
## Task 5: Vite 설정 + tsconfig 갱신
**Files:**
- Create: `vite.config.ts`, `tsconfig.node.json`
- Modify: `tsconfig.json`
- [ ] **Step 1: `vite.config.ts` 생성**
```ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
jsxImportSource: '@emotion/react',
babel: {
plugins: ['@emotion/babel-plugin'],
},
}),
],
base: './',
resolve: {
alias: {
'@': '/src',
},
},
server: {
port: 5173,
strictPort: false,
open: true,
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'state': ['zustand'],
},
},
},
},
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
},
});
```
- [ ] **Step 2: `@emotion/babel-plugin` devDependency 추가**
```bash
npm install -D @emotion/babel-plugin
```
- [ ] **Step 3: `tsconfig.node.json` 생성** (Vite config 자체의 타입체크용)
```json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"types": ["node"]
},
"include": ["vite.config.ts"]
}
```
- [ ] **Step 4: `tsconfig.json` 교체**
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src", "vite-env.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
```
- [ ] **Step 5: Vite 환경변수 타입 선언 파일 생성**
`vite-env.d.ts`:
```ts
/// <reference types="vite/client" />
declare const __APP_VERSION__: string;
interface ImportMetaEnv {
readonly VITE_PUBLIC_URL: string;
readonly VITE_AD_ADAPTER: string;
readonly VITE_GA_MEASUREMENT_ID: string;
readonly VITE_ENABLE_PUSH: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
```
- [ ] **Step 6: 커밋**
```bash
git add vite.config.ts tsconfig.json tsconfig.node.json vite-env.d.ts package.json package-lock.json
git commit -m "feat: add Vite config and TypeScript settings for web build"
```
---
## Task 6: index.html 생성 (Vite 진입점)
**Files:**
- Create: `index.html`
- [ ] **Step 1: 루트에 `index.html` 생성**
```html
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#FF6B35" />
<link rel="icon" type="image/png" href="/app-icon-512.png" />
<title>Archetype: First Spark</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; overscroll-behavior: none; -webkit-tap-highlight-color: transparent; }
body { font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, sans-serif; background: #f7f8fa; }
#root { height: 100%; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
```
- [ ] **Step 2: `app-icon-512.png`가 루트에 있는지 확인** (Vite는 루트 정적 파일을 자동 제공)
Run: `ls app-icon-512.png`
Expected: 파일 존재. 없으면 `app-icon.png`을 사용하도록 `index.html`의 href만 수정.
- [ ] **Step 3: 커밋**
```bash
git add index.html
git commit -m "feat: add Vite index.html entry point"
```
---
## Task 7: TDS adaptive 색상 shim 작성
`@toss/tds-colors``adaptive`를 라이트 모드 고정값으로 대체. `gameColors.ts`와 8개 컴포넌트가 사용하는 11개 키 모두 포함.
**Files:**
- Create: `src/styles/adaptive.ts`
- [ ] **Step 1: `src/styles/adaptive.ts` 생성**
```ts
export const adaptive = {
background: '#ffffff',
greyBackground: '#f7f8fa',
grey100: '#f1f3f5',
grey200: '#e5e8eb',
grey300: '#d1d6db',
grey400: '#b0b8c1',
grey500: '#8b95a1',
grey600: '#6b7684',
grey700: '#4e5968',
grey900: '#191f28',
blue500: '#3182f6',
} as const;
```
- [ ] **Step 2: 커밋**
```bash
git add src/styles/adaptive.ts
git commit -m "feat: add local adaptive color shim to replace @toss/tds-colors"
```
---
## Task 8: 8개 파일에서 adaptive import 경로 교체
기능 변경 0. import 경로만 `@toss/tds-colors``../styles/adaptive` 또는 `../../styles/adaptive` (상대 경로)로 변경.
**Files:**
- Modify: `src/styles/gameColors.ts`, `src/components/AchievementToast.tsx`, `src/components/OfflineRewardModal.tsx`, `src/components/PrestigeModal.tsx`, `src/components/screens/AchievementsScreen.tsx`, `src/components/screens/ElementsScreen.tsx`, `src/components/screens/SettingsScreen.tsx`, `src/components/screens/ShopScreen.tsx`
- [ ] **Step 1: `src/styles/gameColors.ts` line 1 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from './adaptive';`
- [ ] **Step 2: `src/components/AchievementToast.tsx` line 3 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../styles/adaptive';`
- [ ] **Step 3: `src/components/OfflineRewardModal.tsx` line 2 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../styles/adaptive';`
- [ ] **Step 4: `src/components/PrestigeModal.tsx` line 3 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../styles/adaptive';`
- [ ] **Step 5: `src/components/screens/AchievementsScreen.tsx` line 3 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../../styles/adaptive';`
- [ ] **Step 6: `src/components/screens/ElementsScreen.tsx` line 3 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../../styles/adaptive';`
- [ ] **Step 7: `src/components/screens/SettingsScreen.tsx` line 3 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../../styles/adaptive';`
- [ ] **Step 8: `src/components/screens/ShopScreen.tsx` line 3 교체**
Find: `import { adaptive } from '@toss/tds-colors';`
Replace with: `import { adaptive } from '../../styles/adaptive';`
- [ ] **Step 9: 남아있는 `@toss/tds-colors` 임포트가 없는지 확인**
Run: `grep -rn "@toss/tds-colors" src/ || echo OK`
Expected: `OK` (출력 없음)
- [ ] **Step 10: 커밋**
```bash
git add src/styles/gameColors.ts src/components
git commit -m "refactor: route adaptive colors through local shim"
```
---
## Task 9: 환경변수 단일 진입점 `src/config/env.ts`
**Files:**
- Create: `src/config/env.ts`
- [ ] **Step 1: `src/config/env.ts` 생성**
```ts
export const env = {
publicUrl: import.meta.env.VITE_PUBLIC_URL ?? '',
adAdapter: import.meta.env.VITE_AD_ADAPTER ?? 'dummy',
gaMeasurementId: import.meta.env.VITE_GA_MEASUREMENT_ID ?? '',
enablePush: import.meta.env.VITE_ENABLE_PUSH === 'true',
appVersion: __APP_VERSION__,
isDev: import.meta.env.DEV,
isProd: import.meta.env.PROD,
} as const;
```
- [ ] **Step 2: 환경변수 예제 파일 생성** (`.env.example`)
```bash
# Vite 빌드 환경변수 템플릿. 실제 값은 .env.local 또는 .env.production에 설정.
VITE_PUBLIC_URL=
VITE_AD_ADAPTER=dummy
VITE_GA_MEASUREMENT_ID=
VITE_ENABLE_PUSH=false
```
- [ ] **Step 3: `.env.local`을 `.gitignore`에 추가 (이미 있으면 skip)**
`.gitignore``.env.local` 줄이 없으면 추가.
- [ ] **Step 4: 커밋**
```bash
git add src/config/env.ts .env.example .gitignore
git commit -m "feat: add domain-independent env config entry point"
```
---
## Task 10: 분석 어댑터 골격 (`src/platform/analytics/`)
기존 `src/analytics.ts`를 어댑터 인터페이스 뒤에 둔다. v1에서는 `consoleAdapter`만 동작 (실 GA4 통합은 Plan 4).
**Files:**
- Create: `src/platform/analytics/types.ts`, `src/platform/analytics/consoleAdapter.ts`, `src/platform/analytics/index.ts`
- Delete: `src/analytics.ts`
- [ ] **Step 1: `src/platform/analytics/types.ts` 생성**
```ts
export type AnalyticsParams = Record<string, string | number | boolean | null | undefined>;
export interface AnalyticsAdapter {
init(): void;
track(eventName: string, params?: AnalyticsParams): void;
}
```
- [ ] **Step 2: `src/platform/analytics/consoleAdapter.ts` 생성**
```ts
import type { AnalyticsAdapter, AnalyticsParams } from './types';
export function createConsoleAdapter(debug: boolean): AnalyticsAdapter {
return {
init() {
if (debug) console.log('[Analytics] console adapter initialized');
},
track(eventName: string, params: AnalyticsParams = {}) {
if (debug) console.log('[Analytics]', eventName, params);
},
};
}
```
- [ ] **Step 3: `src/platform/analytics/index.ts` 생성** (기존 호출부 호환)
```ts
import { createConsoleAdapter } from './consoleAdapter';
import type { AnalyticsAdapter, AnalyticsParams } from './types';
import { env } from '../../config/env';
let _adapter: AnalyticsAdapter | null = null;
export function initAnalytics(debug = env.isDev): void {
_adapter = createConsoleAdapter(debug);
_adapter.init();
}
export function trackGameEvent(eventName: string, params: AnalyticsParams = {}): void {
_adapter?.track(eventName, params);
}
export type { AnalyticsAdapter, AnalyticsParams };
```
- [ ] **Step 4: 기존 `src/analytics.ts` 삭제**
```bash
rm src/analytics.ts
```
- [ ] **Step 5: 커밋**
```bash
git add src/platform src/analytics.ts
git commit -m "refactor: introduce analytics adapter behind src/platform/"
```
---
## Task 11: 게임 진입점 — `src/main.tsx` + `src/App.tsx`
기존 `pages/index.tsx`의 내용을 `src/App.tsx`로 옮기고, ReactDOM 마운트는 `src/main.tsx`에서 한다.
**Files:**
- Create: `src/main.tsx`, `src/App.tsx`
- [ ] **Step 1: `src/App.tsx` 생성** (기존 `pages/index.tsx` 내용 + import 경로 + analytics 경로 수정)
```tsx
import { css } from '@emotion/react';
import { useEffect } from 'react';
import { BottomTabBar } from './components/BottomTabBar';
import { OfflineRewardModal } from './components/OfflineRewardModal';
import { AchievementToast } from './components/AchievementToast';
import { ElementsScreen } from './components/screens/ElementsScreen';
import { EvolutionScreen } from './components/screens/EvolutionScreen';
import { FusionScreen } from './components/screens/FusionScreen';
import { ShopScreen } from './components/screens/ShopScreen';
import { AchievementsScreen } from './components/screens/AchievementsScreen';
import { SettingsScreen } from './components/screens/SettingsScreen';
import { TutorialOverlay } from './components/tutorial/TutorialOverlay';
import { useIdleTick } from './hooks/useIdleTick';
import { useGameStore } from './store/useGameStore';
import { trackGameEvent } from './platform/analytics';
const rootStyle = css`
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f7f8fa;
overflow: hidden;
font-family:
'Pretendard',
-apple-system,
BlinkMacSystemFont,
sans-serif;
`;
const contentStyle = css`
flex: 1;
overflow-y: auto;
padding-bottom: 72px;
`;
export function App() {
const { activeTab, setActiveTab, elements, gold, elementLevels } = useGameStore();
useIdleTick();
useEffect(() => {
const ownedCount = Object.values(elements).filter((c) => c > 0).length;
const totalLevel = Object.values(elementLevels).reduce((sum, lv) => sum + lv, 0);
trackGameEvent('app_open', {
platform: 'web',
platform_time: new Date().toISOString(),
owned_element_count: ownedCount,
gold,
total_enhance_level: totalLevel,
});
}, []);
return (
<div css={rootStyle}>
<OfflineRewardModal />
<AchievementToast />
<div css={contentStyle}>
{activeTab === 'elements' && <ElementsScreen />}
{activeTab === 'evolution' && <EvolutionScreen />}
{activeTab === 'fusion' && <FusionScreen />}
{activeTab === 'shop' && <ShopScreen />}
{activeTab === 'achievements' && <AchievementsScreen />}
{activeTab === 'settings' && <SettingsScreen />}
</div>
<BottomTabBar activeTab={activeTab} onTabChange={setActiveTab} />
<TutorialOverlay />
</div>
);
}
```
주의: 기존 `_app.tsx``<TutorialOverlay />``App.tsx` 안으로 가져왔다. Provider 래핑은 제거.
- [ ] **Step 2: `src/main.tsx` 생성**
```tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import { initAnalytics } from './platform/analytics';
import { useGameStore, flushGameState } from './store/useGameStore';
initAnalytics();
// 탭 종료/이탈 시 즉시 저장 (throttledStorage 보호)
window.addEventListener('beforeunload', flushGameState);
window.addEventListener('pagehide', flushGameState);
// 첫 진입 시 오프라인 보상 계산
useGameStore.getState().initOffline();
const rootEl = document.getElementById('root');
if (!rootEl) throw new Error('Root element #root not found in index.html');
createRoot(rootEl).render(
<StrictMode>
<App />
</StrictMode>
);
```
- [ ] **Step 3: 커밋**
```bash
git add src/main.tsx src/App.tsx
git commit -m "feat: add Vite entry point and root App component"
```
---
## Task 12: TypeScript 컴파일 통과 확인
신규 코드와 기존 코드의 타입 정합성 검증.
**Files:** 없음 (검증)
- [ ] **Step 1: 타입 체크 실행**
Run: `npm run typecheck`
Expected: 에러 0개. 출력 없음 또는 `tsc -b` 성공 메시지.
- [ ] **Step 2: 에러 발생 시 대응**
가장 흔한 에러 후보:
- `Cannot find module '@toss/tds-colors'` → Task 8 Step 9의 grep 재확인, 누락 파일 수정
- `Cannot find module '@apps-in-toss/...'` → 어딘가 누락된 import. grep으로 찾아 제거.
- `Property 'css' does not exist on type ...` → JSX runtime 설정 누락. `tsconfig.json``jsxImportSource: "@emotion/react"` 확인.
에러 수정 후 재실행. 모두 통과할 때까지 반복.
- [ ] **Step 3: 성공 시 작은 마커 커밋 (선택, 작업 진행 표시용)**
```bash
git commit --allow-empty -m "chore: typecheck passes after Vite migration"
```
---
## Task 13: Vite dev server 부팅 + 브라우저 검증
**Files:** 없음 (검증)
- [ ] **Step 1: dev server 실행**
Run: `npm run dev`
Expected: `Local: http://localhost:5173/` 또는 가용 포트로 부팅. 콘솔에 컴파일 에러 없음.
- [ ] **Step 2: 브라우저에서 접속**
브라우저로 `http://localhost:5173/` 열기. 게임 화면이 표시되는지 확인.
- [ ] **Step 3: 콘솔 에러 확인**
DevTools → Console 열고 빨간색 에러 메시지가 없는지 확인. `[Analytics] app_open ...` 같은 로그는 정상.
- [ ] **Step 4: dev server 종료**
터미널에서 `Ctrl+C`.
---
## Task 14: 수동 회귀 테스트 체크리스트
dev server에서 모든 기존 기능이 회귀 없이 동작하는지 확인. 결과를 plan 문서 하단에 기록.
**Files:** 없음 (검증)
- [ ] **Step 1: dev server 재실행**
Run: `npm run dev`
- [ ] **Step 2: 다음 기능을 순서대로 검증, 각 항목 PASS/FAIL 기록**
브라우저 localStorage를 비우고 (`localStorage.clear()` after F12 console) 새로고침 한 뒤 처음부터 시작:
1. **튜토리얼 단계 1~5 진행** — TutorialOverlay 표시·다음 단계 진행 동작
2. **Elements 탭** — 4원소 인벤토리 표시, 캐릭터 스프라이트 렌더링, FloatingOverlay 동작
3. **Fusion 탭** — 두 슬롯 선택 → 합성 → 결과 원소 추가 + 골드 획득 확인
4. **Evolution(강화) 탭** — 원소 선택 → 강화 비용 골드 차감 + 레벨 업
5. **Shop 탭** — 부스트 구매 → activeBoosts 활성화 + tickIdle에서 2배 적용
6. **Achievements 탭** — 합성·강화 직후 새 업적이 토스트로 표시 + 잠금해제 목록에 추가
7. **Settings 탭** — 언어 토글(ko↔en) 저장, BGM 토글 저장
8. **방치형 동작** — 30초 대기 → 골드/원소 자동 증가 (DevTools에서 store 상태로도 확인 가능)
9. **오프라인 보상** — 탭 닫고 1분 후 재진입 → OfflineRewardModal 표시 + 클레임 동작
10. **프레스티지**`creation`, `spirit` 1개씩 인벤토리에 추가 (DevTools에서 `useGameStore.setState`로 강제) → PrestigeModal 동작 + 횟수 증가 + 칭호 변경
11. **저장/복원** — 새로고침 후에도 진행도 유지 (localStorage 키 `archetype-game-state`)
12. **F12 콘솔에 에러 없음**
- [ ] **Step 3: 결과를 plan 문서 하단의 "Regression Test Log" 섹션에 기록**
각 항목 옆에 `PASS` 또는 `FAIL: <이유>` 추가. FAIL이 있으면 다음 step에서 fix.
- [ ] **Step 4: FAIL 항목이 있으면 추적 + 수정 후 재테스트**
회귀가 발생하면 직전 commit과 비교하여 차이를 찾는다. 흔한 원인:
- import 경로 실수
- `pages/index.tsx`에 있던 어떤 코드가 `App.tsx` 이전 시 누락
- emotion css 설정 미스 (vite.config의 babel.plugins 확인)
전 항목 PASS될 때까지 반복.
---
## Task 15: production build 통과 확인
**Files:** 없음 (검증)
- [ ] **Step 1: production build 실행**
Run: `npm run build`
Expected: `dist/` 디렉토리 생성 + 에러 0. 경고는 허용 (예: chunk size warnings).
- [ ] **Step 2: build 산출물 확인**
Run: `ls dist/`
Expected: `index.html`, `assets/` 디렉토리, JS·CSS 번들 파일들
- [ ] **Step 3: preview server로 build 검증**
Run: `npm run preview`
브라우저로 표시된 URL(보통 `http://localhost:4173/`) 접속. Task 14의 핵심 기능 (튜토리얼, 합성, 강화) 3개만 빠르게 재검증.
- [ ] **Step 4: preview 종료**
`Ctrl+C`
- [ ] **Step 5: build 산출물은 커밋하지 않음 확인**
Run: `git status`
Expected: `dist/` 디렉토리가 untracked로 보이지 않거나, .gitignore에 의해 무시됨. 추적되고 있다면 .gitignore 수정.
- [ ] **Step 6: 마커 커밋**
```bash
git commit --allow-empty -m "chore: production build verified after Vite migration"
```
---
## Task 16: 회귀 테스트 결과 기록 + Plan 1 종료
**Files:**
- Modify: `docs/superpowers/plans/2026-04-27-week1-engine-migration.md` (이 파일의 하단 섹션)
- [ ] **Step 1: 이 plan 문서의 "Regression Test Log" 섹션을 채운다**
아래 템플릿을 plan 문서 끝에 추가:
```markdown
---
## Regression Test Log
**Tested at:** YYYY-MM-DD HH:MM
**Browser:** Chrome 12X / Safari / ...
**Build:** dev / production preview
| # | 기능 | 결과 | 비고 |
|---|------|------|------|
| 1 | 튜토리얼 1~5단계 | | |
| 2 | Elements 탭 | | |
| 3 | Fusion 탭 | | |
| 4 | Evolution 탭 | | |
| 5 | Shop 탭 | | |
| 6 | Achievements 탭 | | |
| 7 | Settings 탭 | | |
| 8 | 방치형 자동 수입 | | |
| 9 | 오프라인 보상 | | |
| 10 | 프레스티지 | | |
| 11 | localStorage 저장/복원 | | |
| 12 | 콘솔 에러 없음 | | |
```
- [ ] **Step 2: plan 문서 커밋**
```bash
git add docs/superpowers/plans/2026-04-27-week1-engine-migration.md
git commit -m "docs: log regression test results for Plan 1"
```
- [ ] **Step 3: 작업 브랜치를 master로 머지**
```bash
git switch master
git merge --no-ff feat/vite-migration -m "feat: complete Week 1 engine migration to Vite (JSA-2)"
```
- [ ] **Step 4: 최종 상태 확인**
Run: `git log --oneline -10`
Expected: 마이그레이션 커밋들이 master에 머지되어 있음. `pre-vite-migration` 태그는 그대로 보존.
- [ ] **Step 5: Plan 2(Week 2 PWA + Push)로 진행 준비 완료**
Plan 1은 여기서 종료. 다음 step은 `superpowers:writing-plans`를 다시 호출하여 Plan 2 작성.
---
## Definition of Done (Plan 1)
- [ ] `git tag pre-vite-migration` 존재 (롤백 안전망)
- [ ] `package.json``@granite-js/*`, `@apps-in-toss/*`, `@toss/*`, `react-native` 의존성 모두 제거됨
- [ ] `babel.config.js`, `granite.config.ts`, `require.context.ts`, `global.d.ts`, `pages/`, `src/_app.tsx`, `src/analytics.ts` 모두 삭제됨
- [ ] `npm run typecheck` 통과
- [ ] `npm run dev` 부팅 성공
- [ ] `npm run build` 성공, `dist/` 산출
- [ ] `npm run preview` 부팅 성공
- [ ] Regression Test Log의 12개 항목 모두 PASS
- [ ] master 브랜치에 머지 완료