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:
881
docs/superpowers/plans/2026-04-27-week1-engine-migration.md
Normal file
881
docs/superpowers/plans/2026-04-27-week1-engine-migration.md
Normal 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 브랜치에 머지 완료
|
||||
Reference in New Issue
Block a user