# 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`, `src/components/screens/EvolutionScreen.tsx`, `src/components/screens/FusionScreen.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/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`
- Modify: `src/components/OfflineRewardModal.tsx`, `src/components/screens/ShopScreen.tsx`, `src/components/screens/EvolutionScreen.tsx`, `src/components/screens/FusionScreen.tsx` (line 5/6/11/8 each — analytics import path)

- [ ] **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: 4개 consumer 파일의 analytics import 경로 갱신**

```bash
# 다음 4개 파일에서 import 경로 변경:
# src/components/OfflineRewardModal.tsx 5행
# src/components/screens/ShopScreen.tsx 6행
# src/components/screens/EvolutionScreen.tsx 11행
# src/components/screens/FusionScreen.tsx 8행
#
# 변경 내용 (각 파일):
#   '../analytics'        → '../platform/analytics'
#   '../../analytics'     → '../../platform/analytics'
```

검증:
- Grep `from '../analytics'` 또는 `from '../../analytics'` 결과 0개여야 함
- Grep `from '../platform/analytics'` 또는 `from '../../platform/analytics'` 결과 4개여야 함

- [ ] **Step 6: 커밋**

```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 브랜치에 머지 완료
