✨ feat: 공통 Claude 지침 및 규칙 문서 추가
This commit is contained in:
70
.cursorrules
Normal file
70
.cursorrules
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Nuxt 프로젝트 — Cursor 규칙
|
||||||
|
|
||||||
|
## 언어
|
||||||
|
|
||||||
|
- 사용자와의 대화·설명·커밋 메시지 본문은 **한국어**를 사용한다.
|
||||||
|
|
||||||
|
## Git 커밋 메시지 (필수)
|
||||||
|
|
||||||
|
- **제목은 한 줄**, [Conventional Commits](https://www.conventionalcommits.org/) + **이모지**를 함께 쓴다.
|
||||||
|
- 형식: `<이모지> <type>: <한글 설명>` (이모지·타입·콜론 뒤 공백 한 칸)
|
||||||
|
- **설명(제목 본문)은 반드시 한글**로 작성한다. 영어 제목만 단독으로 쓰지 않는다.
|
||||||
|
- **명령형** 어조 (`추가`, `수정` — `추가됨`, `수정함` 지양).
|
||||||
|
- **첫 줄(제목)은 72자 미만**을 권장한다.
|
||||||
|
- **원자적 커밋**: 한 커밋에 단일 목적만 담는다. 관련 없는 변경은 분할한다.
|
||||||
|
- 커밋 생성 시 **Claude·AI 서명·Co-authored-by 등 메타 서명을 본문에 넣지 않는다.**
|
||||||
|
|
||||||
|
### 커밋 생성 프로세스 (`/commit` 등 요청 시)
|
||||||
|
|
||||||
|
1. **스테이지된 파일**이 있으면 그 파일만 대상으로 커밋한다. 없으면 사용자에게 스테이징 여부를 확인한다.
|
||||||
|
2. `git diff` 등으로 **논리적 변경 덩어리**를 분석한다.
|
||||||
|
3. 타입이 섞였거나 관심사가 다르면 **분할 커밋**을 제안한다.
|
||||||
|
4. 아래 **이모지 맵**과 타입에 맞춰 제목을 만든다.
|
||||||
|
|
||||||
|
### 타입 (type)
|
||||||
|
|
||||||
|
| type | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| `feat` | 새 기능 |
|
||||||
|
| `fix` | 버그 수정 |
|
||||||
|
| `docs` | 문서 |
|
||||||
|
| `style` | 포맷팅·세미콜론 등 의미 없는 스타일 |
|
||||||
|
| `refactor` | 리팩터링 |
|
||||||
|
| `perf` | 성능 개선 |
|
||||||
|
| `test` | 테스트 |
|
||||||
|
| `chore` | 빌드·도구·잡무 |
|
||||||
|
| `ci` | CI |
|
||||||
|
| `build` | 빌드 시스템·번들러 |
|
||||||
|
|
||||||
|
### 이모지 맵 (타입·맥락에 맞게 선택)
|
||||||
|
|
||||||
|
✨ feat | 🐛 fix | 📝 docs | 💄 style | ♻️ refactor | ⚡ perf | ✅ test | 🔧 chore | 🚀 ci | 🚨 warnings | 🔒️ security | 🚚 move | 🏗️ architecture | ➕ add-dep | ➖ remove-dep | 🌱 seed | 🧑💻 dx | 🏷️ types | 👔 business | 🚸 ux | 🩹 minor-fix | 🥅 errors | 🔥 remove | 🎨 structure | 🚑️ hotfix | 🎉 init | 🔖 release | 🚧 wip | 💚 ci-fix | 📌 pin-deps | 👷 ci-build | 📈 analytics | ✏️ typos | ⏪️ revert | 📄 license | 💥 breaking | 🍱 assets | ♿️ accessibility | 💡 comments | 🗃️ db | 🔊 logs | 🔇 remove-logs | 🙈 gitignore | 📸 snapshots | ⚗️ experiment | 🚩 flags | 💫 animations | ⚰️ dead-code | 🦺 validation | ✈️ offline
|
||||||
|
|
||||||
|
### 분할 제안 기준
|
||||||
|
|
||||||
|
- 서로 다른 **관심사**가 한 diff에 섞인 경우
|
||||||
|
- **타입**이 혼합된 경우 (예: `feat` + `fix`)
|
||||||
|
- **파일 패턴**이 완전히 다른 영역(예: 앱 코드 vs 인프라만)인 경우
|
||||||
|
- **변경량이 크고** 커밋 단위로 나눌 수 있는 경우
|
||||||
|
|
||||||
|
### 예시
|
||||||
|
|
||||||
|
- `✨ feat: 로그인 폼 유효성 검사 추가`
|
||||||
|
- `♻️ refactor: 사용자 API 호출 로직을 composable로 분리`
|
||||||
|
- `🐛 fix: 모바일에서 헤더가 겹치는 문제 수정`
|
||||||
|
|
||||||
|
### 본문이 필요할 때
|
||||||
|
|
||||||
|
- 제목 아래 빈 줄 후 본문을 한글 bullet 또는 문단으로 적는다.
|
||||||
|
|
||||||
|
## 코드·작업 방식
|
||||||
|
|
||||||
|
- 요청 범위 밖의 리팩터·포맷 일괄 변경·무관 파일 수정을 하지 않는다.
|
||||||
|
- 기존 코드의 네이밍, import 스타일, 타입·주석 수준에 맞춘다.
|
||||||
|
- 변경 이유가 드러나는 **작고 집중된 diff**를 선호한다.
|
||||||
|
|
||||||
|
## Cursor 사용 시
|
||||||
|
|
||||||
|
- 파일을 수정하기 전에 관련 맥락(주변 코드·설정)을 읽고 일관되게 맞춘다.
|
||||||
|
- 사용자가 명시적으로 요청하지 않은 README·문서 파일은 새로 쓰거나 크게 늘리지 않는다.
|
||||||
|
|
||||||
71
CLAUDE.md
71
CLAUDE.md
@@ -1,62 +1,39 @@
|
|||||||
# CLAUDE.md
|
# 팀 공통 Claude 지침 (fe-common-rules)
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
이 파일은 팀 전체에서 공통으로 사용하는 Claude 작업 지침의 **엔트리 포인트**입니다.
|
||||||
|
각 프로젝트의 `CLAUDE.md`에서 `@.claude/common/CLAUDE.md` 형태로 참조하여 사용합니다.
|
||||||
|
|
||||||
## Project Overview
|
## 적용 범위
|
||||||
|
|
||||||
- **Type**: Game service frontend agent (submodule-based monorepo)
|
- 팀 내 모든 프론트엔드 프로젝트
|
||||||
- **Stack**: Nuxt 3/4, TypeScript (strict), pnpm
|
- Claude Code / Claude 기반 자동화 작업 전반
|
||||||
- **Language**: Korean (코드 리뷰, 커밋 메시지, 문서)
|
|
||||||
|
|
||||||
## Commands
|
## 포함된 지침
|
||||||
|
|
||||||
### Development
|
아래 문서들은 Claude가 이 파일을 읽을 때 자동으로 함께 로드됩니다.
|
||||||
```bash
|
|
||||||
pnpm install # Install dependencies
|
|
||||||
pnpm dev # Run dev server
|
|
||||||
pnpm build # Production build
|
|
||||||
pnpm preview # Preview build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Validation
|
@rules/coding-conventions.md
|
||||||
```bash
|
|
||||||
pnpm build # Verify no build errors
|
|
||||||
pnpm typecheck # Verify TypeScript (if configured)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Submodules
|
@rules/framework-rules.md
|
||||||
```bash
|
|
||||||
git submodule update --init --recursive # Initialize
|
|
||||||
git submodule update --remote --recursive # Update all
|
|
||||||
```
|
|
||||||
|
|
||||||
## Git Workflow
|
@rules/commit-pr.md
|
||||||
|
|
||||||
### Branches
|
@rules/claude-workflow.md
|
||||||
- `release` - Production (verified)
|
|
||||||
- `dev` - Development/testing
|
|
||||||
- Feature branches from `dev`
|
|
||||||
|
|
||||||
### MR Guidelines
|
## 우선순위
|
||||||
- **Title**: `[Category] Description` (Feature, Fix, Refactor, Docs)
|
|
||||||
- **Target**: `dev` first, then `release` after verification
|
|
||||||
- **Review**: 1+ team member approval required
|
|
||||||
|
|
||||||
## Context Files
|
1. 프로젝트 `CLAUDE.md`에 명시된 **프로젝트 지침**이 최우선입니다.
|
||||||
|
2. 이 공통 지침은 프로젝트 지침과 충돌하지 않는 범위 내에서 항상 적용됩니다.
|
||||||
|
3. 충돌이 발생하면 프로젝트 지침을 따르되, 차이를 사용자에게 알려주세요.
|
||||||
|
|
||||||
Additional context in `.claude/` directory:
|
## 공통 Skill
|
||||||
- `contexts/` - Nuxt patterns, TypeScript conventions
|
|
||||||
- `skills/` - code-review, test-generator, refactor-component
|
|
||||||
- `agents/` - debugger, refactor, performance-optimizer
|
|
||||||
|
|
||||||
## Project-Specific Patterns
|
공통 저장소는 `skills/` 폴더에 팀 공용 Claude skill 들을 함께 관리합니다.
|
||||||
|
프로젝트는 `scripts/link-skills.sh` 로 `.claude/skills/` 에 심볼릭 링크하여 사용합니다.
|
||||||
|
자세한 내용은 `skills/README.md` 를 참고하세요.
|
||||||
|
|
||||||
### Common Nuxt Issues
|
## 업데이트
|
||||||
- Hydration mismatch → Use `<ClientOnly>`
|
|
||||||
- useFetch infinite loop → Check watch/immediate/key options
|
|
||||||
|
|
||||||
### Key Conventions
|
- 공통 지침은 이 레파지토리(`fe-common-rules`)에서만 수정합니다.
|
||||||
- Use Composition API with `<script setup lang="ts">`
|
- 각 프로젝트는 `scripts/update.sh`(또는 `git submodule update --remote`)로 최신 버전을 받아갑니다.
|
||||||
- Composables for reusable logic
|
- 수정 제안은 PR로 받습니다. 자세한 내용은 루트 `README.md` 참고.
|
||||||
- Pinia for complex state management
|
|
||||||
- Always type API responses: `useFetch<Type>()`
|
|
||||||
|
|||||||
316
README.md
316
README.md
@@ -1,162 +1,238 @@
|
|||||||
# gameservice-fe-agent
|
# fe-common-rules
|
||||||
|
|
||||||
게임 서비스 프론트엔드 에이전트 프로젝트
|
팀 전체에서 공통으로 사용하는 **프론트엔드 Claude 지침 저장소**입니다.
|
||||||
|
각 프로젝트는 이 저장소를 **Git submodule**로 포함하고, `CLAUDE.md`의 `@import` 구문으로 공통 지침을 불러와 사용합니다.
|
||||||
|
|
||||||
## 프로젝트 구조
|
---
|
||||||
|
|
||||||
이 프로젝트는 **submodule 방식**으로 관리됩니다.
|
## 📁 저장소 구조
|
||||||
|
|
||||||
## 브랜치 전략
|
```
|
||||||
|
fe-common-rules/
|
||||||
|
├── CLAUDE.md # 공통 지침 엔트리 포인트 (rules/* 를 @import)
|
||||||
|
├── README.md # 이 파일
|
||||||
|
├── rules/
|
||||||
|
│ ├── coding-conventions.md # 포맷팅, 네이밍, 타입
|
||||||
|
│ ├── framework-rules.md # Vue/Nuxt/Tailwind/라이브러리 규칙
|
||||||
|
│ ├── commit-pr.md # 커밋 & PR 규칙
|
||||||
|
│ └── claude-workflow.md # Claude 작업 방식 지침
|
||||||
|
├── templates/
|
||||||
|
│ ├── CLAUDE.md.tpl # 프로젝트 루트 CLAUDE.md 템플릿
|
||||||
|
│ └── project/ # 프로젝트 지침 양식
|
||||||
|
│ ├── overview.md
|
||||||
|
│ ├── conventions.md
|
||||||
|
│ └── architecture.md
|
||||||
|
├── skills/ # 팀 공용 Claude skill 모음
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── vue-component-review/
|
||||||
|
│ │ └── SKILL.md
|
||||||
|
│ └── conventional-commit/
|
||||||
|
│ └── SKILL.md
|
||||||
|
├── scripts/
|
||||||
|
│ ├── install.sh # submodule 설치 + 템플릿 복사 + skill 링크
|
||||||
|
│ ├── init-project.sh # .claude/project 양식만 다시 초기화/diff
|
||||||
|
│ ├── link-skills.sh # 공통 skill 을 .claude/skills 로 링크/해제
|
||||||
|
│ └── update.sh # 최신 공통 지침으로 업데이트
|
||||||
|
└── examples/
|
||||||
|
└── sample-nuxt-project/ # Nuxt4 + Vue3 + TS + Tailwind 샘플
|
||||||
|
```
|
||||||
|
|
||||||
- `release` - 검증 완료 브랜치 (프로덕션 배포용)
|
---
|
||||||
- `dev` - 검증 테스트 브랜치 (개발 및 테스트용)
|
|
||||||
|
|
||||||
## 설치 방법
|
## 🚀 프로젝트에 도입하기
|
||||||
|
|
||||||
|
### 1. Submodule 설치 (최초 1회)
|
||||||
|
|
||||||
|
프로젝트 루트에서 아래 명령을 실행합니다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 저장소 클론 (submodule 포함)
|
# 원격 저장소 URL은 팀 내부 Git 주소로 교체하세요
|
||||||
git clone --recurse-submodules https://git.sginfra.net/sgp-web-d/gameservice-fe-agent.git
|
git submodule add <fe-common-rules-repo-url> .claude/common
|
||||||
|
|
||||||
# 이미 클론한 경우 submodule 초기화
|
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
## Submodule 업데이트
|
또는 이 저장소에 포함된 헬퍼 스크립트를 사용해도 됩니다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 모든 submodule 업데이트
|
curl -fsSL <install.sh raw URL> | bash
|
||||||
git submodule update --remote --recursive
|
|
||||||
|
|
||||||
# 특정 submodule 업데이트
|
|
||||||
git submodule update --remote <submodule-path>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 개발 워크플로우
|
설치 후 프로젝트 구조는 다음과 같이 됩니다.
|
||||||
|
|
||||||
1. `dev` 브랜치에서 개발 및 테스트 진행
|
```
|
||||||
2. 검증 완료 후 `release` 브랜치로 머지
|
your-project/
|
||||||
3. `release` 브랜치에서 프로덕션 배포
|
├── .claude/
|
||||||
|
│ ├── common/ ← submodule (fe-common-rules)
|
||||||
|
│ └── project/ ← 프로젝트 고유 지침
|
||||||
|
├── CLAUDE.md ← 공통 + 프로젝트 지침을 @import
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
## 기여하기
|
### 2. 프로젝트 `CLAUDE.md` 작성
|
||||||
|
|
||||||
### 새로운 기능 추가
|
프로젝트 루트의 `CLAUDE.md`에서 공통 지침과 프로젝트 지침을 함께 불러옵니다.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# <프로젝트 이름>
|
||||||
|
|
||||||
|
## 공통 지침
|
||||||
|
@.claude/common/CLAUDE.md
|
||||||
|
|
||||||
|
## 프로젝트 지침
|
||||||
|
@.claude/project/overview.md
|
||||||
|
@.claude/project/conventions.md
|
||||||
|
```
|
||||||
|
|
||||||
|
> Claude Code는 `@경로` 구문을 만나면 해당 파일의 내용을 컨텍스트에 포함시킵니다.
|
||||||
|
> 공통 지침의 `CLAUDE.md`는 내부적으로 `rules/*.md`를 다시 `@import`하므로,
|
||||||
|
> 한 줄의 `@.claude/common/CLAUDE.md`만으로 모든 공통 지침이 로드됩니다.
|
||||||
|
|
||||||
|
### 3. 공통 지침 업데이트 받기
|
||||||
|
|
||||||
1. **브랜치 생성**
|
|
||||||
```bash
|
```bash
|
||||||
git checkout dev
|
# 최신 main 브랜치의 공통 지침으로 업데이트
|
||||||
git pull origin dev
|
git submodule update --remote .claude/common
|
||||||
git checkout -b feature/your-feature-name
|
|
||||||
|
# 또는 헬퍼 스크립트
|
||||||
|
bash .claude/common/scripts/update.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **개발 및 테스트**
|
업데이트 이후에는 submodule 포인터가 변경되므로 반드시 커밋합니다.
|
||||||
- 기능 개발 및 단위 테스트 작성
|
|
||||||
- 로컬에서 충분한 테스트 진행
|
|
||||||
|
|
||||||
3. **Merge Request 생성**
|
```bash
|
||||||
- 제목: `[Feature] 기능명`
|
git add .claude/common
|
||||||
- 설명: 구현 내용, 변경 사항, 테스트 결과 포함
|
git commit -m "chore: update fe-common-rules submodule"
|
||||||
- 대상 브랜치: `dev`
|
```
|
||||||
|
|
||||||
### Merge Request 작성 규칙
|
|
||||||
|
|
||||||
- **제목 형식**: `[Category] 변경 내용`
|
|
||||||
- Category: `Feature`, `Fix`, `Refactor`, `Docs` 등
|
|
||||||
- 예: `[Feature] 사용자 인증 기능 추가`
|
|
||||||
|
|
||||||
- **설명 포함 사항**:
|
|
||||||
- 변경 이유 및 목적
|
|
||||||
- 주요 변경 내용
|
|
||||||
- 테스트 결과 및 방법
|
|
||||||
- 영향 받는 부분
|
|
||||||
|
|
||||||
### 코드 리뷰 프로세스
|
|
||||||
|
|
||||||
1. **최소 1명의 팀 멤버 승인** 필요
|
|
||||||
2. **체크리스트**:
|
|
||||||
- [ ] 코드 컨벤션 준수
|
|
||||||
- [ ] 단위 테스트 작성 및 통과
|
|
||||||
- [ ] 문서화 완료
|
|
||||||
- [ ] 기존 기능에 영향 없음
|
|
||||||
3. **승인 후 dev 브랜치에 머지**
|
|
||||||
4. **dev 브랜치에서 충분한 검증 후 release 브랜치로 머지**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 관리 방침
|
## 📝 프로젝트 지침 양식 사용
|
||||||
|
|
||||||
### 버전 관리
|
`.claude/project/` 에 들어갈 지침은 공통 저장소의 `templates/project/` 에 양식으로 관리됩니다.
|
||||||
- Git 커밋 히스토리를 통해 모든 변경 이력 추적
|
팀원은 `install.sh` 를 사용하면 submodule 설치와 동시에 이 양식을 자동으로 복사받아 작성을
|
||||||
- Release 브랜치는 태그로 버전 관리 (예: `v1.0.0`)
|
시작할 수 있습니다.
|
||||||
- Dev 브랜치에서 충분한 검증 후 Release로 배포
|
|
||||||
|
|
||||||
### 브랜치 보호
|
### 신규 프로젝트 — install.sh 한 번으로 끝
|
||||||
- **Release 브랜치**: 직접 푸시 금지, MR을 통해서만 머지
|
|
||||||
- **Dev 브랜치**: 기능 브랜치에서 MR을 통해 머지
|
|
||||||
- 모든 머지는 코드 리뷰 필수
|
|
||||||
|
|
||||||
### 문서화
|
```bash
|
||||||
- 주요 변경사항은 CHANGELOG.md에 기록
|
bash .claude/common/scripts/install.sh git@github.com:<org>/fe-common-rules.git
|
||||||
- README는 항상 최신 상태 유지
|
# → .claude/common 에 submodule 설치
|
||||||
- 커밋 메시지는 명확하고 상세하게 작성
|
# → templates/project/*.md 를 .claude/project/ 로 복사
|
||||||
|
# → templates/CLAUDE.md.tpl 을 루트 CLAUDE.md 로 복사
|
||||||
|
```
|
||||||
|
|
||||||
|
### 이미 submodule 이 있는 프로젝트 — init-project.sh
|
||||||
|
|
||||||
|
이미 공통 저장소를 submodule 로 연결해둔 프로젝트에서 `.claude/project/` 양식만 따로
|
||||||
|
초기화하거나, 공통 저장소에 새 양식이 추가됐을 때 사용합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 기본: 없는 파일만 복사 (기존 파일은 건너뜀)
|
||||||
|
bash .claude/common/scripts/init-project.sh
|
||||||
|
|
||||||
|
# 기존 파일을 덮어쓰고 싶을 때
|
||||||
|
bash .claude/common/scripts/init-project.sh --force
|
||||||
|
|
||||||
|
# 차이만 확인하고 복사는 하지 않기
|
||||||
|
bash .claude/common/scripts/init-project.sh --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
### 양식을 추가/수정하려면
|
||||||
|
|
||||||
|
공통 저장소의 `templates/project/*.md` 를 수정하는 PR 을 올립니다. 머지 후 팀원들은
|
||||||
|
`git submodule update --remote` 로 새 양식을 받은 뒤, 필요하면
|
||||||
|
`init-project.sh --diff` 로 자신의 프로젝트 지침과 비교할 수 있습니다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 사용 예시
|
## 🛠 공통 Skill 사용
|
||||||
|
|
||||||
### 코드 리뷰 Skill 사용
|
Claude Code 는 프로젝트의 `.claude/skills/<skill-name>/SKILL.md` 를 자동으로 탐색합니다.
|
||||||
|
공통 저장소는 `skills/` 폴더에 팀 공용 skill 들을 두고, 각 프로젝트는 **심볼릭 링크**로
|
||||||
|
이를 `.claude/skills/` 에 연결합니다. 링크 방식이기 때문에 `git submodule update` 한 번으로
|
||||||
|
모든 프로젝트가 최신 skill 을 자동으로 사용하게 됩니다.
|
||||||
|
|
||||||
```markdown
|
### 설치 (install.sh 사용 시 자동)
|
||||||
<!-- .claude/skills/code-review.md -->
|
|
||||||
# Code Review Skill
|
|
||||||
|
|
||||||
## 목적
|
`install.sh` 는 submodule 추가, 템플릿 복사, **공통 skill 링크** 까지 한 번에 처리합니다.
|
||||||
PR의 코드를 리뷰하고 개선사항을 제안합니다.
|
별도 조치가 필요하지 않습니다.
|
||||||
|
|
||||||
## 사용법
|
### 이미 설치된 프로젝트 — link-skills.sh
|
||||||
1. PR 번호를 제공
|
|
||||||
2. Claude가 변경된 파일을 분석
|
|
||||||
3. 코딩 컨벤션, 성능, 보안 관점에서 리뷰
|
|
||||||
4. 개선사항 제안
|
|
||||||
|
|
||||||
## 예시
|
|
||||||
`/code-review #123`
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nuxt 개발 가이드 활용
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
<!-- .claude/contexts/nuxt-best-practices.md -->
|
|
||||||
# Nuxt 베스트 프랙티스
|
|
||||||
|
|
||||||
## 컴포넌트 작성
|
|
||||||
- Composition API 사용
|
|
||||||
- Composables로 로직 재사용
|
|
||||||
- Auto-import 활용으로 간결한 코드 작성
|
|
||||||
|
|
||||||
## 성능 최적화
|
|
||||||
- 동적 import로 코드 스플리팅
|
|
||||||
- useFetch, useAsyncData로 데이터 최적화
|
|
||||||
- Lazy loading 컴포넌트 활용
|
|
||||||
```
|
|
||||||
|
|
||||||
### 기술 스택
|
|
||||||
|
|
||||||
- **Framework**: Nuxt 3, Nuxt 4
|
|
||||||
- **Language**: TypeScript
|
|
||||||
- **Package Manager**: pnpm (권장)
|
|
||||||
|
|
||||||
### 개발 환경
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 의존성 설치
|
# 모든 공통 skill 을 .claude/skills/ 로 링크 (기존 링크는 건너뜀)
|
||||||
pnpm install
|
bash .claude/common/scripts/link-skills.sh
|
||||||
|
|
||||||
# 개발 서버 실행
|
# 실제 링크 없이 어떤 작업이 일어날지 미리보기
|
||||||
pnpm dev
|
bash .claude/common/scripts/link-skills.sh --dry-run
|
||||||
|
|
||||||
# 프로덕션 빌드
|
# 기존 링크/폴더를 덮어쓰기
|
||||||
pnpm build
|
bash .claude/common/scripts/link-skills.sh --force
|
||||||
|
|
||||||
# 프로덕션 미리보기
|
# 특정 skill 하나만 링크
|
||||||
pnpm preview
|
bash .claude/common/scripts/link-skills.sh vue-component-review
|
||||||
|
|
||||||
|
# 공통 skill 링크 전체 해제 (프로젝트 고유 skill 은 건드리지 않음)
|
||||||
|
bash .claude/common/scripts/link-skills.sh --unlink
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 프로젝트 고유 skill 과의 공존
|
||||||
|
|
||||||
|
`.claude/skills/` 아래에는 공통 skill 링크와 **프로젝트 자체 skill 폴더**가 함께 있을 수
|
||||||
|
있습니다. `link-skills.sh` 는 심볼릭 링크(`-L`)인 항목만 관리하므로 프로젝트 고유 skill
|
||||||
|
은 덮어쓰지 않습니다.
|
||||||
|
|
||||||
|
```
|
||||||
|
.claude/skills/
|
||||||
|
├── vue-component-review → 링크: ../common/skills/vue-component-review
|
||||||
|
├── conventional-commit → 링크: ../common/skills/conventional-commit
|
||||||
|
└── my-project-only-skill/ ← 이 프로젝트만의 skill (실제 폴더)
|
||||||
|
└── SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 새 skill 을 공통 저장소에 추가
|
||||||
|
|
||||||
|
1. `skills/<skill-name>/SKILL.md` 를 작성 (YAML frontmatter 에 `name`, `description` 필수)
|
||||||
|
2. PR 을 올리고 팀 리뷰
|
||||||
|
3. 머지 후 각 프로젝트에서 아래 실행
|
||||||
|
```bash
|
||||||
|
git submodule update --remote .claude/common
|
||||||
|
bash .claude/common/scripts/link-skills.sh # 새로 추가된 skill 이 자동 링크됨
|
||||||
|
git add .gitmodules .claude
|
||||||
|
git commit -m "chore: sync fe-common-rules"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✍️ 공통 지침 수정 프로세스
|
||||||
|
|
||||||
|
1. 이 저장소를 별도로 clone 합니다.
|
||||||
|
2. 브랜치를 만들고 `rules/*.md` 혹은 `CLAUDE.md`를 수정합니다.
|
||||||
|
3. PR을 열고 팀 리뷰를 받습니다.
|
||||||
|
4. 머지 후 각 프로젝트에서 `git submodule update --remote` 로 동기화합니다.
|
||||||
|
|
||||||
|
수정 시 가이드
|
||||||
|
|
||||||
|
- **기존 규칙을 바꿀 때는 이유를 PR 설명에 반드시 기록**합니다.
|
||||||
|
- 특정 프로젝트에만 해당되는 내용은 공통 지침이 아닌 프로젝트 지침으로 분리합니다.
|
||||||
|
- 충돌 가능성이 있는 규칙은 기본값을 정하고, 프로젝트에서 오버라이드 가능하도록 문장을 구성합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧭 지침 우선순위
|
||||||
|
|
||||||
|
Claude가 동시에 여러 지침을 읽는 경우 다음 우선순위를 따릅니다.
|
||||||
|
|
||||||
|
1. **프로젝트 지침 (`.claude/project/*.md`)** — 최우선
|
||||||
|
2. **공통 지침 (`.claude/common/*.md`)** — 프로젝트와 충돌하지 않는 범위 내에서 적용
|
||||||
|
3. Claude의 일반 원칙
|
||||||
|
|
||||||
|
충돌이 발생하면 Claude는 프로젝트 지침을 따르되, 차이를 사용자에게 명시적으로 알려야 합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 샘플 프로젝트
|
||||||
|
|
||||||
|
`examples/sample-nuxt-project/` 폴더에 Nuxt4 + Vue3 + TypeScript + Tailwind CSS 기반의
|
||||||
|
샘플 구성이 들어 있습니다. 프로젝트에 이 저장소를 연결하는 실제 예시를 보려면 해당 폴더의
|
||||||
|
`README.md`를 참고하세요.
|
||||||
|
|||||||
@@ -1,353 +0,0 @@
|
|||||||
# 프로모션 - 기본 템플릿 Zeplin MCP 마크업 작업 가이드
|
|
||||||
|
|
||||||
이 문서는 Zeplin MCP를 활용한 프로모션 페이지 마크업 작업 방식을 정리한 가이드입니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 환경 설정 (최초 1회)
|
|
||||||
|
|
||||||
### Zeplin MCP SSL 인증서 오류 해결
|
|
||||||
|
|
||||||
사내 네트워크/VPN 환경에서 Zeplin MCP가 `unable to get local issuer certificate` 오류를 발생시키는 경우, `~/.cursor/mcp.json`의 Zeplin 서버 `env`에 아래 항목을 추가해야 합니다.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"zeplin": {
|
|
||||||
"timeout": 600,
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@zeplin/mcp-server@latest"],
|
|
||||||
"env": {
|
|
||||||
"ZEPLIN_ACCESS_TOKEN": "...",
|
|
||||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
설정 변경 후 **Cursor 재시작** 필요 (`Cmd+Shift+P` → `Developer: Reload Window`).
|
|
||||||
|
|
||||||
> `NODE_TLS_REJECT_UNAUTHORIZED=0`은 로컬 Cursor MCP 전용 설정이며 소스코드/배포 환경과 완전히 분리됩니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 프로젝트 구조
|
|
||||||
|
|
||||||
```
|
|
||||||
l9-fe/
|
|
||||||
├── pages/promotion/{프로젝트명}/{날짜}/index.vue
|
|
||||||
├── assets/scss/pages/promotion/{프로젝트명}/{날짜}/index.scss
|
|
||||||
└── public/assets/promotion/{프로젝트명}/{날짜}/
|
|
||||||
├── pc/
|
|
||||||
│ ├── bg_section01_ko.jpg
|
|
||||||
│ ├── bg_section01_tw.jpg
|
|
||||||
│ └── ...
|
|
||||||
└── mobile/
|
|
||||||
├── bg_section01.jpg ← 기본 BG (언어 무관, 배경 전용 화면에서 다운로드)
|
|
||||||
├── bg_section01_ko.jpg ← KO 콘텐츠 이미지 (KO 콘텐츠 화면에서 다운로드)
|
|
||||||
├── bg_section01_tw.jpg ← TW 콘텐츠 이미지 (TW 콘텐츠 화면에서 다운로드)
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vue 파일 주요 구조
|
|
||||||
|
|
||||||
- `promotion-content` 클래스: 프로젝트별 고유 클래스명 사용 (예: `dinapheri-update`)
|
|
||||||
- 섹션 구성: `section01` ~ `sectionN`
|
|
||||||
- 번역 키: `tm()` 함수로 호출, JSON 파일에서 관리
|
|
||||||
- SCSS import: `<style>` 블록에서 `@import '@/assets/scss/pages/promotion/.../index.scss'`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Zeplin 화면 종류와 역할
|
|
||||||
|
|
||||||
프로모션 페이지는 용도별로 여러 Zeplin 화면이 존재합니다. 작업 전에 각 화면의 역할을 먼저 파악하세요.
|
|
||||||
|
|
||||||
### PC 화면
|
|
||||||
|
|
||||||
| 화면 역할 | 내용 |
|
|
||||||
|----------|------|
|
|
||||||
| PC KR 배경+콘텐츠 | PC용 전체 화면. 섹션 배경 이미지 + 텍스트 레이어 위치 좌표 확인용 |
|
|
||||||
| PC TW 배경+콘텐츠 | TW 언어용 PC 전체 화면 |
|
|
||||||
|
|
||||||
### 모바일 화면 (3종)
|
|
||||||
|
|
||||||
모바일은 PC와 달리 **역할이 분리된 화면 3종**을 사용합니다.
|
|
||||||
|
|
||||||
| 화면 역할 | 파일명 규칙 | 설명 |
|
|
||||||
|----------|-----------|------|
|
|
||||||
| 모바일 BG 전용 (고해상도) | `bg_sectionXX.jpg` | 배경 이미지만 있는 화면. 보통 2048px 너비. `.view-mobile section`의 `background-image`에 사용 |
|
|
||||||
| 모바일 KO 콘텐츠 | `bg_sectionXX_ko.jpg` | 배경 + KO 콘텐츠가 합성된 화면. `[lang='ko-KR'] section::before`의 `background-image`에 사용 |
|
|
||||||
| 모바일 TW 콘텐츠 | `bg_sectionXX_tw.jpg` | 배경 + TW 콘텐츠가 합성된 화면. `[lang='zh-TW'] section::before`의 `background-image`에 사용 |
|
|
||||||
|
|
||||||
> **섹션 1개당 이미지 3개** 사용: `bg_sectionXX.jpg` + `bg_sectionXX_ko.jpg` + `bg_sectionXX_tw.jpg`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Zeplin MCP 사용법
|
|
||||||
|
|
||||||
### 화면 레이어 조회
|
|
||||||
|
|
||||||
```
|
|
||||||
get_screen(url, includeVariants: false)
|
|
||||||
```
|
|
||||||
|
|
||||||
조회 결과에서 확인할 항목:
|
|
||||||
- 각 섹션 그룹 레이어의 `sourceId`, `rect.y`, `rect.height`
|
|
||||||
- 텍스트 레이어의 절대 `y` 좌표 (notice, even-date 위치 계산용)
|
|
||||||
- `exportable: true` 여부 → false이면 다운로드 불가 (디자이너에게 export 설정 요청)
|
|
||||||
|
|
||||||
### 레이어 구조 파악 방법
|
|
||||||
|
|
||||||
`get_screen`으로 조회하면 최상위 레이어 목록과 각 레이어의 `y`, `height` 값을 확인할 수 있습니다.
|
|
||||||
섹션별 절대 y 시작값과 높이는 **프로젝트마다 다르므로**, 작업 전 반드시 전체 화면을 조회해서 직접 확인하세요.
|
|
||||||
|
|
||||||
```
|
|
||||||
예시)
|
|
||||||
레이어명 섹션 절대 y 시작 높이
|
|
||||||
00_main → section01 0 1020
|
|
||||||
01_con → section02 1020 2449
|
|
||||||
02_con → section03 3469 1839
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 요소 위치 계산 공식
|
|
||||||
|
|
||||||
**PC** - CSS `top` (px 고정값):
|
|
||||||
```
|
|
||||||
CSS top = 텍스트 레이어 절대 y − 해당 섹션 절대 y 시작값
|
|
||||||
|
|
||||||
예시) section03의 notice 텍스트
|
|
||||||
절대 y: 2830 − section03 시작 y: 1923 = top: 907px
|
|
||||||
```
|
|
||||||
|
|
||||||
**모바일** - `toPercentage(섹션높이, 상대y)`:
|
|
||||||
```
|
|
||||||
상대 y = 텍스트 레이어 절대 y − 해당 섹션 절대 y 시작값
|
|
||||||
CSS top = toPercentage(섹션높이, 상대y)
|
|
||||||
|
|
||||||
예시) section03의 even-date (섹션 y: 3469, 섹션 높이: 1839)
|
|
||||||
텍스트 절대 y: 3697
|
|
||||||
상대 y: 3697 - 3469 = 228
|
|
||||||
→ toPercentage(1839, 228)
|
|
||||||
```
|
|
||||||
|
|
||||||
> 모바일 텍스트 좌표는 **콘텐츠 화면(KO/TW)**에서 읽어야 정확합니다. BG 전용 화면에는 텍스트 레이어가 없습니다.
|
|
||||||
|
|
||||||
### 이미지 에셋 다운로드
|
|
||||||
|
|
||||||
```
|
|
||||||
download_layer_asset(layerSourceId, localPath, assetType)
|
|
||||||
```
|
|
||||||
|
|
||||||
- 다운로드 시 **UUID 파일명**으로 저장되므로 반드시 rename 필요
|
|
||||||
- `exportable: false`인 레이어는 다운로드 불가 → 디자이너에게 Zeplin export 설정 요청 후 재시도
|
|
||||||
- Zeplin 디자인이 수정되면 `sourceId`가 바뀔 수 있으므로 교체 시 화면 재조회 필수
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. SCSS 작성 규칙
|
|
||||||
|
|
||||||
### 전체 구조
|
|
||||||
|
|
||||||
```scss
|
|
||||||
/* ─── PC ─────────────────────────────── */
|
|
||||||
.view-desktop {
|
|
||||||
.promotion-content.{프로젝트클래스} {
|
|
||||||
section { /* 공통 PC 스타일 */ }
|
|
||||||
.notice-list { /* 공통 */ }
|
|
||||||
|
|
||||||
.section01 { height: 826px; }
|
|
||||||
.section02 {
|
|
||||||
height: 1100px;
|
|
||||||
}
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── 모바일 ──────────────────────────── */
|
|
||||||
.view-mobile {
|
|
||||||
.promotion-content.{프로젝트클래스} {
|
|
||||||
section {
|
|
||||||
position: relative;
|
|
||||||
background-position: 50% 0;
|
|
||||||
background-size: auto 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
max-width: toRem(750);
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: 50% 0 / 100% auto no-repeat;
|
|
||||||
}
|
|
||||||
.notice-list { /* 공통 모바일 notice-list 스타일 */ }
|
|
||||||
.even-date { /* 공통 모바일 even-date 스타일 */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 섹션별 개별 설정 */
|
|
||||||
.section01 {
|
|
||||||
background-image: url('/assets/.../mobile/bg_section01.jpg'); /* 기본 BG */
|
|
||||||
&::before { @include inputMobileHeight(1020); } /* 섹션 높이값 */
|
|
||||||
}
|
|
||||||
.section02 {
|
|
||||||
background-image: url('/assets/.../mobile/bg_section02.jpg');
|
|
||||||
&::before { @include inputMobileHeight(2449); }
|
|
||||||
.notice-list { top: toPercentage(2449, 2319); }
|
|
||||||
}
|
|
||||||
.section03 {
|
|
||||||
background-image: url('/assets/.../mobile/bg_section03.jpg');
|
|
||||||
&::before { @include inputMobileHeight(1839); }
|
|
||||||
.even-date { top: toPercentage(1839, 228); }
|
|
||||||
.notice-list { top: toPercentage(1839, 1673); }
|
|
||||||
}
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── 언어별 이미지 ────────────────────── */
|
|
||||||
[lang='ko-KR'] {
|
|
||||||
.view-desktop .promotion-content.{프로젝트클래스} {
|
|
||||||
.section01 { background-image: url('/assets/.../pc/bg_section01_ko.jpg'); }
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
.view-mobile .promotion-content.{프로젝트클래스} {
|
|
||||||
/* ::before 에 KO 콘텐츠 이미지 적용 */
|
|
||||||
.section01::before { background-image: url('/assets/.../mobile/bg_section01_ko.jpg'); }
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[lang='zh-TW'] {
|
|
||||||
.view-desktop .promotion-content.{프로젝트클래스} {
|
|
||||||
.section01 { background-image: url('/assets/.../pc/bg_section01_tw.jpg'); }
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
.view-mobile .promotion-content.{프로젝트클래스} {
|
|
||||||
.section01::before { background-image: url('/assets/.../mobile/bg_section01_tw.jpg'); }
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 모바일 이미지 레이어링 원리 (모바일 전용)
|
|
||||||
|
|
||||||
> 아래 구조는 **모바일에만 해당**합니다. PC는 section에 배경 이미지 하나만 사용합니다.
|
|
||||||
|
|
||||||
```
|
|
||||||
[section 요소 자체]
|
|
||||||
background-image: bg_sectionXX.jpg
|
|
||||||
→ 화면 전체를 채우는 배경 깔판 (언어 무관)
|
|
||||||
|
|
||||||
[section::before 요소] ← section 위에 겹쳐지는 콘텐츠 레이어
|
|
||||||
background-image: bg_sectionXX_ko.jpg ([lang='ko-KR'] 블록)
|
|
||||||
or bg_sectionXX_tw.jpg ([lang='zh-TW'] 블록)
|
|
||||||
→ max-width: 750px, margin: 0 auto 로 중앙 정렬
|
|
||||||
→ padding-top % 로 이미지 비율 유지 (inputMobileHeight mixin)
|
|
||||||
→ 배경 위에 KO/TW 콘텐츠 이미지가 가운데 자리잡음
|
|
||||||
```
|
|
||||||
|
|
||||||
즉, 배경(bg_sectionXX.jpg)은 section에, 언어별 콘텐츠 이미지(bg_sectionXX_ko/tw.jpg)는 ::before에 분리해서 적용합니다.
|
|
||||||
|
|
||||||
**PC는 section에 언어별 배경 이미지 하나만 사용** (`[lang] section { background-image: bg_sectionXX_ko.jpg }`).
|
|
||||||
|
|
||||||
### inputMobileHeight 값
|
|
||||||
|
|
||||||
`inputMobileHeight(N)` 의 `N`은 **Zeplin BG 전용 화면의 섹션 높이(px)**에서 읽어옵니다.
|
|
||||||
콘텐츠 화면과 BG 화면의 섹션 높이는 동일하므로 어느 쪽에서 읽어도 무방합니다.
|
|
||||||
|
|
||||||
### 공통 요소 스타일
|
|
||||||
|
|
||||||
| 요소 | PC 주요 스타일 | 모바일 주요 스타일 |
|
|
||||||
|------|-------------|----------------|
|
|
||||||
| `.notice-list ul li` | `font-size: 16px`, `line-height: 26px`, `color: #978c7a` | `font-size: toRem(10)`, `padding-left: toRem(12)` |
|
|
||||||
| `.notice-list li::before` | `content: '•'`, `position: absolute`, `left: 0`, `top: 50%`, `transform: translateY(-50%)` | 동일 |
|
|
||||||
| `.notice-link` | `font-weight: 600`, `color: #b08442`, `text-decoration: underline` | 동일 |
|
|
||||||
| `.even-date` | `font-size: 20px`, `color: rgba(156,148,132,0.97)` | `font-size: rem(12)`, `letter-spacing: -0.8px` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 마크업 패턴
|
|
||||||
|
|
||||||
### 기본 섹션 구조 (section01 참고)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<section id="section01" class="section01">
|
|
||||||
<h3 class="blind">
|
|
||||||
<span>{{ tm('ProjectName_Section01_SubTitle') }}</span>
|
|
||||||
<em>{{ tm('ProjectName_Section01_Title') }}</em>
|
|
||||||
</h3>
|
|
||||||
<p class="blind">{{ tm('ProjectName_Section01_Desc') }}</p>
|
|
||||||
<button type="button" class="btn-partner"
|
|
||||||
@click="[sendLog('event-name', 'UI-1'), goToLink(tm('ProjectName_Section01_Btn_Url'), '_blank')]">
|
|
||||||
<span class="blind">{{ tm('ProjectName_Section01_Btn') }}</span>
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 이벤트 섹션 구조 (notice-list + even-date 패턴)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<section id="section03" class="section03">
|
|
||||||
<h4 class="blind">{{ tm('ProjectName_Section03_Title') }}</h4>
|
|
||||||
<p class="blind">{{ tm('ProjectName_Section03_Event_Rewards') }}</p>
|
|
||||||
<p class="even-date">{{ tm('ProjectName_Section03_Date') }}</p>
|
|
||||||
<div class="notice-list">
|
|
||||||
<ul>
|
|
||||||
<template v-for="(notice, noticeIdx) in tm('ProjectName_Section03_Notice_List')" :key="noticeIdx">
|
|
||||||
<!-- 마지막 항목에만 링크 포함 -->
|
|
||||||
<li v-if="noticeIdx === tm('ProjectName_Section03_Notice_List').length - 1">
|
|
||||||
<span v-dompurify-html="tm(`${notice}`)"></span>
|
|
||||||
<span v-dompurify-html="tm(`${notice}_Link`)"
|
|
||||||
class="notice-link"
|
|
||||||
@click="sendLog('event-name', 'UI-XX')">
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li v-else v-dompurify-html="tm(`${notice}`)"></li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 작업 순서 (새 세션 시작 시)
|
|
||||||
|
|
||||||
### PC 작업
|
|
||||||
|
|
||||||
1. 이 문서 숙지
|
|
||||||
2. PC KR 화면 `get_screen` 조회 → 섹션별 레이어 y, height, sourceId 확인
|
|
||||||
3. 각 섹션 텍스트 레이어 절대 y → 상대 y 계산 (`top: px`)
|
|
||||||
4. PC KR 이미지 다운로드 → `bg_sectionXX_ko.jpg`로 rename
|
|
||||||
5. PC TW 화면 조회 → TW 이미지 다운로드 → `bg_sectionXX_tw.jpg`로 rename
|
|
||||||
6. Vue 마크업 작성
|
|
||||||
7. SCSS PC 스타일 작성 → 언어별 이미지 경로 블록 추가
|
|
||||||
|
|
||||||
### 모바일 추가 작업
|
|
||||||
|
|
||||||
8. **모바일 BG 전용 화면** 조회 → 섹션 높이(`inputMobileHeight` 값) 확인
|
|
||||||
- 이미지 다운로드 → `bg_sectionXX.jpg`로 rename
|
|
||||||
9. **모바일 KO 콘텐츠 화면** 조회 → 텍스트 레이어 절대 y 확인
|
|
||||||
- `상대 y = 텍스트 절대 y − 섹션 시작 y` 계산
|
|
||||||
- `toPercentage(섹션높이, 상대y)` 값 SCSS에 적용
|
|
||||||
- 이미지 다운로드 → `bg_sectionXX_ko.jpg`로 rename
|
|
||||||
10. **모바일 TW 콘텐츠 화면** 조회 (필요 시)
|
|
||||||
- 이미지 다운로드 → `bg_sectionXX_tw.jpg`로 rename
|
|
||||||
11. SCSS 모바일 스타일 작성:
|
|
||||||
- `.view-mobile section` 공통 스타일
|
|
||||||
- 섹션별 `background-image`, `inputMobileHeight`, `toPercentage` 값
|
|
||||||
- `[lang='ko-KR']` 블록: `section::before { background-image: ..._ko.jpg }`
|
|
||||||
- `[lang='zh-TW']` 블록: `section::before { background-image: ..._tw.jpg }`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 주의사항
|
|
||||||
|
|
||||||
- **`exportable: false` 레이어는 다운로드 불가** → 디자이너에게 Zeplin에서 export 설정 요청 후 재시도
|
|
||||||
- **Zeplin 디자인 수정 시 sourceId 변경 가능** → 이미지 교체 시 반드시 화면 재조회
|
|
||||||
- **모바일 텍스트 좌표는 콘텐츠 화면에서 읽을 것** → BG 전용 화면에는 텍스트 레이어 없음
|
|
||||||
- **`inputMobileHeight` 값 = BG 화면의 섹션 height** → 콘텐츠 화면과 동일하므로 어느 쪽이든 무방
|
|
||||||
- **번역 키**는 별도 JSON 파일에서 관리 (`tm()` 함수로 호출)
|
|
||||||
- **`blind` 클래스**는 스크린리더 전용 숨김 텍스트
|
|
||||||
- SCSS mixin: `inputMobileHeight`, `inputMobileSize`, `toPercentage`, `toRem`, `rem` 등은 프로젝트 공통 정의 사용
|
|
||||||
62
rules/claude-workflow.md
Normal file
62
rules/claude-workflow.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Claude 작업 방식 지침
|
||||||
|
|
||||||
|
이 문서는 Claude가 팀 프로젝트에서 작업할 때 따라야 할 일반적인 원칙을 정의합니다.
|
||||||
|
|
||||||
|
## 기본 원칙
|
||||||
|
|
||||||
|
1. **기존 코드 존중**: 수정 전에 관련 파일과 주변 컨벤션을 먼저 파악합니다.
|
||||||
|
2. **최소 변경**: 요구사항을 충족하는 최소한의 변경만 수행합니다. 관련 없는 리팩토링은 별도 작업으로 분리합니다.
|
||||||
|
3. **가정 대신 질문**: 요구사항이 모호하면 추측하지 말고 사용자에게 확인합니다.
|
||||||
|
4. **근거 있는 수정**: 코드 변경의 이유를 설명할 수 있어야 합니다.
|
||||||
|
|
||||||
|
## 작업 순서
|
||||||
|
|
||||||
|
1. **탐색 (Explore)**
|
||||||
|
- 관련 파일을 먼저 읽고 프로젝트 구조를 파악합니다.
|
||||||
|
- 유사한 패턴이 이미 존재하는지 확인합니다.
|
||||||
|
|
||||||
|
2. **계획 (Plan)**
|
||||||
|
- 여러 파일을 수정하거나 복잡한 작업이면 할 일 목록을 만들어 공유합니다.
|
||||||
|
- 아키텍처에 영향을 주는 변경은 착수 전에 사용자 승인을 받습니다.
|
||||||
|
|
||||||
|
3. **구현 (Implement)**
|
||||||
|
- 한 번에 하나의 논리적 변경에 집중합니다.
|
||||||
|
- 공통 지침과 프로젝트 지침을 모두 준수합니다.
|
||||||
|
|
||||||
|
4. **검증 (Verify)**
|
||||||
|
- 린트 / 타입체크 / 빌드가 깨지지 않는지 확인합니다.
|
||||||
|
- 테스트가 있는 프로젝트라면 관련 테스트를 실행합니다.
|
||||||
|
- 수동 검증이 필요한 경우 확인 방법을 사용자에게 안내합니다.
|
||||||
|
|
||||||
|
## 해서는 안 되는 것
|
||||||
|
|
||||||
|
- **임의 기능 추가 금지**: 사용자가 요청하지 않은 기능을 추가하지 않습니다.
|
||||||
|
- **기존 코드 대량 리팩토링 금지**: 요청 범위를 벗어나는 변경은 하지 않습니다.
|
||||||
|
- **주석 / 문서 임의 삭제 금지**: 불필요해 보여도 삭제 전 사용자에게 확인합니다.
|
||||||
|
- **비밀정보 출력 금지**: 환경변수, 키, 토큰 등은 코드에 하드코딩하지 않습니다.
|
||||||
|
- **의존성 버전 임의 변경 금지**: 요청이 없다면 `package.json`의 버전을 변경하지 않습니다.
|
||||||
|
- **강제 푸시 / 히스토리 재작성 금지**: `push --force`, `reset --hard` 등은 사용자의 명시적 요청 없이 실행하지 않습니다.
|
||||||
|
|
||||||
|
## 커뮤니케이션
|
||||||
|
|
||||||
|
- 답변은 간결하게, 결론을 먼저 말합니다.
|
||||||
|
- 코드를 수정했다면 **어떤 파일을 어떻게 바꿨는지** 요약합니다.
|
||||||
|
- 불확실한 부분은 솔직하게 밝히고 대안을 제시합니다.
|
||||||
|
- 긴 설명보다 실제 결과물(코드/파일)을 우선합니다.
|
||||||
|
|
||||||
|
## 파일 작업 원칙
|
||||||
|
|
||||||
|
- 새 파일 생성보다 **기존 파일 수정**을 우선합니다.
|
||||||
|
- README, 문서는 사용자가 명시적으로 요청했을 때만 생성합니다.
|
||||||
|
- 파일을 읽지 않고 수정하지 않습니다.
|
||||||
|
- 대량 변경 시에는 diff를 확인할 수 있도록 단계별로 진행합니다.
|
||||||
|
|
||||||
|
## 질문이 필요한 상황
|
||||||
|
|
||||||
|
다음의 경우 반드시 사용자에게 확인을 요청합니다.
|
||||||
|
|
||||||
|
- 요구사항의 일부가 불명확할 때
|
||||||
|
- 여러 구현 방식이 있고 각각 장단점이 뚜렷할 때
|
||||||
|
- 공통 지침과 프로젝트 지침이 충돌할 때
|
||||||
|
- 파괴적 작업(파일 삭제, 데이터 마이그레이션, 스키마 변경 등)이 필요할 때
|
||||||
|
- 외부 서비스 호출이나 결제 관련 작업일 때
|
||||||
50
rules/coding-conventions.md
Normal file
50
rules/coding-conventions.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 코딩 컨벤션
|
||||||
|
|
||||||
|
## 기본 원칙
|
||||||
|
|
||||||
|
- **가독성 우선**: 영리한 코드보다 읽기 쉬운 코드를 선호합니다.
|
||||||
|
- **일관성 유지**: 기존 코드의 스타일을 먼저 관찰하고 그에 맞춥니다.
|
||||||
|
- **작은 단위**: 함수와 파일은 한 가지 책임만 지도록 작게 유지합니다.
|
||||||
|
|
||||||
|
## 포맷팅
|
||||||
|
|
||||||
|
- 들여쓰기: 스페이스 2칸 (탭 사용 금지)
|
||||||
|
- 문자열: 싱글 쿼터(`'`) 사용, JSX/템플릿 속성값은 더블 쿼터(`"`)
|
||||||
|
- 세미콜론: 생략하지 않고 항상 작성
|
||||||
|
- 줄 끝 공백 제거, 파일 끝 개행 1줄 유지
|
||||||
|
- 한 줄 최대 100자 (초과 시 줄바꿈)
|
||||||
|
- Prettier 설정 파일(`.prettierrc`)이 있는 경우 해당 설정을 우선합니다.
|
||||||
|
|
||||||
|
## 네이밍
|
||||||
|
|
||||||
|
- **변수/함수**: `camelCase` (예: `userProfile`, `fetchUserData`)
|
||||||
|
- **상수**: `UPPER_SNAKE_CASE` (예: `MAX_RETRY_COUNT`)
|
||||||
|
- **컴포넌트/클래스/타입**: `PascalCase` (예: `UserCard`, `OrderStatus`)
|
||||||
|
- **파일명**
|
||||||
|
- Vue 컴포넌트: `PascalCase.vue` (예: `UserCard.vue`)
|
||||||
|
- Composable: `use` 접두사 + `camelCase` (예: `useAuth.ts`)
|
||||||
|
- 일반 TS 모듈: `kebab-case.ts` (예: `format-date.ts`)
|
||||||
|
- **이벤트 핸들러**: `handle` 또는 `on` 접두사 (예: `handleClick`, `onSubmit`)
|
||||||
|
- **불리언**: `is`, `has`, `can`, `should` 접두사 (예: `isLoading`, `hasError`)
|
||||||
|
|
||||||
|
## 타입
|
||||||
|
|
||||||
|
- `any` 사용 금지. 불가피할 경우 주석으로 이유를 남기고 `unknown`을 먼저 고려합니다.
|
||||||
|
- 함수 시그니처에는 매개변수와 반환 타입을 명시합니다.
|
||||||
|
- 공개 API(타 모듈에서 import 되는 것)는 반드시 타입을 export 합니다.
|
||||||
|
- 유니온 타입은 `as const` 또는 별도 타입 alias로 관리합니다.
|
||||||
|
|
||||||
|
## 주석
|
||||||
|
|
||||||
|
- "무엇을" 보다 "왜"를 설명합니다.
|
||||||
|
- TODO/FIXME 주석에는 작성자와 날짜 또는 이슈 번호를 포함합니다.
|
||||||
|
- 공개 함수/컴포넌트에는 JSDoc 한 줄 설명을 권장합니다.
|
||||||
|
|
||||||
|
## import 순서
|
||||||
|
|
||||||
|
1. 외부 라이브러리 (예: `vue`, `nuxt`)
|
||||||
|
2. 내부 절대 경로 (예: `~/components/...`)
|
||||||
|
3. 상대 경로 (예: `./utils`)
|
||||||
|
4. 타입 only import는 각 그룹 내에서 별도 블록으로 분리
|
||||||
|
|
||||||
|
그룹 사이에는 빈 줄을 한 줄 둡니다.
|
||||||
83
rules/commit-pr.md
Normal file
83
rules/commit-pr.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# 커밋 / PR 규칙
|
||||||
|
|
||||||
|
## 커밋 메시지
|
||||||
|
|
||||||
|
[Conventional Commits](https://www.conventionalcommits.org/)를 따릅니다.
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### type
|
||||||
|
|
||||||
|
- `feat`: 새로운 기능 추가
|
||||||
|
- `fix`: 버그 수정
|
||||||
|
- `refactor`: 기능 변화 없는 구조 개선
|
||||||
|
- `style`: 코드 포맷/세미콜론 등 스타일 변경
|
||||||
|
- `docs`: 문서 수정
|
||||||
|
- `test`: 테스트 추가/수정
|
||||||
|
- `chore`: 빌드, 설정, 패키지 업데이트 등
|
||||||
|
- `perf`: 성능 개선
|
||||||
|
- `ci`: CI 설정 변경
|
||||||
|
|
||||||
|
### 작성 규칙
|
||||||
|
|
||||||
|
- **subject**는 50자 이내, 명령형 현재 시제(예: `add`, `fix` — `added`, `fixes` 아님)
|
||||||
|
- subject 끝에 마침표를 찍지 않습니다.
|
||||||
|
- body는 "무엇을"보다 "왜"를 설명합니다. 한 줄 72자 이내로 줄바꿈합니다.
|
||||||
|
- 한 커밋에는 하나의 논리적 변경만 담습니다.
|
||||||
|
|
||||||
|
### 예시
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(user): add profile image upload
|
||||||
|
|
||||||
|
프로필 이미지 업로드 요구사항에 따라 multipart 업로드 경로를 추가했습니다.
|
||||||
|
기존 텍스트 필드 업데이트 API는 변경하지 않았습니다.
|
||||||
|
|
||||||
|
Refs: #123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pull Request
|
||||||
|
|
||||||
|
### 제목
|
||||||
|
|
||||||
|
커밋 메시지와 동일한 컨벤션을 따릅니다. (`<type>(<scope>): <subject>`)
|
||||||
|
|
||||||
|
### 본문 템플릿
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 변경 사항
|
||||||
|
- 무엇이 바뀌었는지 요약
|
||||||
|
|
||||||
|
## 배경 / 이유
|
||||||
|
- 왜 이 변경이 필요했는지
|
||||||
|
|
||||||
|
## 테스트
|
||||||
|
- 어떻게 검증했는지 (수동/자동 테스트 내용)
|
||||||
|
|
||||||
|
## 스크린샷 (UI 변경 시)
|
||||||
|
- Before / After
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
- [ ] 로컬에서 빌드/테스트 통과
|
||||||
|
- [ ] 린트/포맷 통과
|
||||||
|
- [ ] 공통 지침(fe-common-rules) 준수
|
||||||
|
- [ ] 관련 문서 업데이트
|
||||||
|
```
|
||||||
|
|
||||||
|
### 리뷰 기준
|
||||||
|
|
||||||
|
- 최소 1명 이상의 승인 필요
|
||||||
|
- CI(Lint / Test / Build) 전부 통과 필요
|
||||||
|
- 머지 전략은 **Squash and merge**를 기본으로 합니다.
|
||||||
|
- 리뷰어는 변경 범위에 대해 질문이 남지 않도록 배경을 충분히 이해한 뒤 승인합니다.
|
||||||
|
|
||||||
|
### Draft PR
|
||||||
|
|
||||||
|
- 작업 중간 중간 피드백이 필요한 경우 Draft로 먼저 올리는 것을 권장합니다.
|
||||||
|
- Draft 상태에서는 CI 실패가 있어도 괜찮습니다.
|
||||||
47
rules/framework-rules.md
Normal file
47
rules/framework-rules.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 프레임워크 / 라이브러리 사용 규칙
|
||||||
|
|
||||||
|
## Vue 3 / Nuxt
|
||||||
|
|
||||||
|
### 컴포넌트 작성
|
||||||
|
|
||||||
|
- **`<script setup lang="ts">` 사용**을 기본으로 합니다. Options API는 신규 코드에서 사용하지 않습니다.
|
||||||
|
- 컴포넌트는 단일 책임 원칙을 지키며, 200줄을 넘으면 분리를 검토합니다.
|
||||||
|
- Props는 `defineProps<T>()` 제네릭 형태로 타입을 명시합니다.
|
||||||
|
- Emits는 `defineEmits<{ ... }>()` 제네릭 형태로 선언합니다.
|
||||||
|
- `ref` vs `reactive`: 원시값과 단일 객체는 `ref`, 복잡한 상태 트리는 `reactive`를 고려합니다. 일관성을 위해 팀 내에서 가능한 `ref`를 우선합니다.
|
||||||
|
|
||||||
|
### 상태 관리
|
||||||
|
|
||||||
|
- 컴포넌트 내 로컬 상태: `ref` / `reactive`
|
||||||
|
- 여러 컴포넌트가 공유하는 상태: **Pinia** 사용
|
||||||
|
- 서버 상태: Nuxt `useFetch` / `useAsyncData` 사용, 직접 `fetch` 호출은 지양합니다.
|
||||||
|
|
||||||
|
### 라우팅
|
||||||
|
|
||||||
|
- Nuxt의 파일 기반 라우팅을 사용합니다. 수동 라우트 정의는 특수한 경우에만 허용됩니다.
|
||||||
|
- 동적 라우트 파라미터는 `[param].vue` 형식을 사용합니다.
|
||||||
|
|
||||||
|
### Composable
|
||||||
|
|
||||||
|
- 재사용 가능한 로직은 `composables/` 디렉토리의 `useXxx` 함수로 추출합니다.
|
||||||
|
- Composable은 부수효과를 최소화하고, 반환 객체에 상태와 메서드를 함께 묶어 반환합니다.
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
- `strict: true`를 유지합니다.
|
||||||
|
- 공용 타입은 `types/` 또는 각 도메인의 `types.ts`에 모아둡니다.
|
||||||
|
- 외부 API 응답은 반드시 타입을 정의하여 사용합니다.
|
||||||
|
|
||||||
|
## Tailwind CSS
|
||||||
|
|
||||||
|
- **유틸리티 클래스 우선** 사용. 공통 패턴은 컴포넌트로 추출합니다.
|
||||||
|
- `@apply`는 꼭 필요한 경우에만 사용하고, 가능한 유틸리티를 직접 나열합니다.
|
||||||
|
- 임의값 클래스(`w-[123px]`)는 디자인 시스템에 등록되지 않은 값에만 제한적으로 사용합니다.
|
||||||
|
- 조건부 클래스는 `clsx` 또는 `cn` 유틸리티를 사용하여 가독성을 확보합니다.
|
||||||
|
- 클래스 순서는 Tailwind 공식 프리셋(`prettier-plugin-tailwindcss`)을 따릅니다.
|
||||||
|
|
||||||
|
## 외부 라이브러리 도입
|
||||||
|
|
||||||
|
- 새로운 라이브러리를 추가할 때는 **PR 설명에 도입 이유, 번들 영향, 대안 검토 내용**을 기록합니다.
|
||||||
|
- 동일 기능의 라이브러리를 중복 도입하지 않습니다.
|
||||||
|
- 유지보수가 중단된 패키지(6개월 이상 업데이트 없음)는 도입하지 않습니다.
|
||||||
96
scripts/init-project.sh
Executable file
96
scripts/init-project.sh
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# fe-common-rules project template initializer
|
||||||
|
# 이미 .claude/common 이 설치된 프로젝트에서 templates/project/ 의
|
||||||
|
# 양식을 .claude/project/ 에 복사합니다.
|
||||||
|
#
|
||||||
|
# 사용법:
|
||||||
|
# bash .claude/common/scripts/init-project.sh # 없는 파일만 복사 (기본)
|
||||||
|
# bash .claude/common/scripts/init-project.sh --force # 기존 파일을 덮어씀
|
||||||
|
# bash .claude/common/scripts/init-project.sh --diff # 차이만 보여주고 복사는 안 함
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
COMMON_PATH=".claude/common"
|
||||||
|
PROJECT_PATH=".claude/project"
|
||||||
|
TEMPLATE_DIR="$COMMON_PATH/templates/project"
|
||||||
|
|
||||||
|
MODE="safe" # safe | force | diff
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--force) MODE="force" ;;
|
||||||
|
--diff) MODE="diff" ;;
|
||||||
|
-h|--help)
|
||||||
|
grep '^#' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "❌ 알 수 없는 옵션: $arg" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
if [[ ! -d "$TEMPLATE_DIR" ]]; then
|
||||||
|
echo "❌ $TEMPLATE_DIR 가 없습니다. 먼저 submodule 을 설치/업데이트하세요:" >&2
|
||||||
|
echo " git submodule update --init --recursive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_PATH"
|
||||||
|
|
||||||
|
echo "📝 templates/project → $PROJECT_PATH (mode=$MODE)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
copied=0
|
||||||
|
skipped=0
|
||||||
|
diffed=0
|
||||||
|
for f in "$TEMPLATE_DIR"/*.md; do
|
||||||
|
name="$(basename "$f")"
|
||||||
|
dest="$PROJECT_PATH/$name"
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
diff)
|
||||||
|
if [[ -f "$dest" ]]; then
|
||||||
|
if ! diff -q "$f" "$dest" >/dev/null 2>&1; then
|
||||||
|
echo "📄 diff: $dest"
|
||||||
|
diff -u "$dest" "$f" || true
|
||||||
|
echo ""
|
||||||
|
diffed=$((diffed + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "➕ 새 양식: $dest (복사되지 않음. --force 로 복사하세요)"
|
||||||
|
diffed=$((diffed + 1))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
force)
|
||||||
|
cp "$f" "$dest"
|
||||||
|
echo " ✅ $dest (덮어씀)"
|
||||||
|
copied=$((copied + 1))
|
||||||
|
;;
|
||||||
|
safe)
|
||||||
|
if [[ -f "$dest" ]]; then
|
||||||
|
echo " ⏭ $dest (이미 존재)"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
else
|
||||||
|
cp "$f" "$dest"
|
||||||
|
echo " ✅ $dest"
|
||||||
|
copied=$((copied + 1))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
case "$MODE" in
|
||||||
|
diff) echo "🔍 차이가 있는 파일: $diffed 개";;
|
||||||
|
*) echo "🎉 완료: 복사 $copied개 / 건너뜀 $skipped개";;
|
||||||
|
esac
|
||||||
135
scripts/install.sh
Executable file
135
scripts/install.sh
Executable file
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# fe-common-rules installer
|
||||||
|
# 현재 Git 프로젝트의 .claude/common 경로에 fe-common-rules 저장소를
|
||||||
|
# submodule 로 추가하고, templates/ 에서 프로젝트 지침 양식과
|
||||||
|
# CLAUDE.md 템플릿을 복사합니다.
|
||||||
|
#
|
||||||
|
# 사용법:
|
||||||
|
# bash scripts/install.sh <repo-url> [<branch>]
|
||||||
|
#
|
||||||
|
# 예:
|
||||||
|
# bash scripts/install.sh git@github.com:our-team/fe-common-rules.git main
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_URL="${1:-}"
|
||||||
|
BRANCH="${2:-main}"
|
||||||
|
TARGET_PATH=".claude/common"
|
||||||
|
PROJECT_PATH=".claude/project"
|
||||||
|
|
||||||
|
if [[ -z "$REPO_URL" ]]; then
|
||||||
|
echo "❌ 사용법: bash scripts/install.sh <repo-url> [branch]" >&2
|
||||||
|
echo " 예: bash scripts/install.sh git@github.com:our-team/fe-common-rules.git main" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Git 프로젝트인지 확인
|
||||||
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다. 먼저 'git init' 후 실행해주세요." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 루트로 이동
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
# 1) Submodule 추가
|
||||||
|
if [[ -d "$TARGET_PATH" ]]; then
|
||||||
|
echo "⚠️ '$TARGET_PATH' 경로가 이미 존재합니다. submodule 추가를 건너뜁니다."
|
||||||
|
else
|
||||||
|
echo "📦 fe-common-rules 를 submodule 로 추가합니다..."
|
||||||
|
git submodule add -b "$BRANCH" "$REPO_URL" "$TARGET_PATH"
|
||||||
|
git submodule update --init --recursive
|
||||||
|
echo "✅ submodule 추가 완료: $TARGET_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) 프로젝트 지침 양식 복사 (templates/project/ → .claude/project/)
|
||||||
|
mkdir -p "$PROJECT_PATH"
|
||||||
|
|
||||||
|
TEMPLATE_DIR="$TARGET_PATH/templates/project"
|
||||||
|
if [[ -d "$TEMPLATE_DIR" ]]; then
|
||||||
|
echo "📝 프로젝트 지침 양식을 복사합니다..."
|
||||||
|
for f in "$TEMPLATE_DIR"/*.md; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
name="$(basename "$f")"
|
||||||
|
dest="$PROJECT_PATH/$name"
|
||||||
|
if [[ -f "$dest" ]]; then
|
||||||
|
echo " ⏭ $dest (이미 존재 - 건너뜀)"
|
||||||
|
else
|
||||||
|
cp "$f" "$dest"
|
||||||
|
echo " ✅ $dest"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "⚠️ $TEMPLATE_DIR 를 찾지 못했습니다. 공통 저장소의 templates 가 오래됐을 수 있습니다."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3) 루트 CLAUDE.md 템플릿 복사
|
||||||
|
if [[ ! -f "CLAUDE.md" ]]; then
|
||||||
|
TPL_FILE="$TARGET_PATH/templates/CLAUDE.md.tpl"
|
||||||
|
if [[ -f "$TPL_FILE" ]]; then
|
||||||
|
cp "$TPL_FILE" CLAUDE.md
|
||||||
|
echo "✅ CLAUDE.md 템플릿을 생성했습니다."
|
||||||
|
else
|
||||||
|
cat > CLAUDE.md <<'EOF'
|
||||||
|
# <프로젝트 이름>
|
||||||
|
|
||||||
|
## 공통 지침
|
||||||
|
@.claude/common/CLAUDE.md
|
||||||
|
|
||||||
|
## 프로젝트 지침
|
||||||
|
@.claude/project/overview.md
|
||||||
|
@.claude/project/conventions.md
|
||||||
|
@.claude/project/architecture.md
|
||||||
|
EOF
|
||||||
|
echo "✅ CLAUDE.md 템플릿을 생성했습니다. (fallback)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ℹ️ 기존 CLAUDE.md 가 이미 존재합니다. 아래 블록을 수동으로 추가하세요:"
|
||||||
|
echo ""
|
||||||
|
echo " ## 공통 지침"
|
||||||
|
echo " @.claude/common/CLAUDE.md"
|
||||||
|
echo ""
|
||||||
|
echo " ## 프로젝트 지침"
|
||||||
|
echo " @.claude/project/overview.md"
|
||||||
|
echo " @.claude/project/conventions.md"
|
||||||
|
echo " @.claude/project/architecture.md"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4) 공통 skill 심볼릭 링크 (.claude/common/skills/* → .claude/skills/*)
|
||||||
|
SKILLS_SRC="$TARGET_PATH/skills"
|
||||||
|
SKILLS_DEST=".claude/skills"
|
||||||
|
|
||||||
|
if [[ -d "$SKILLS_SRC" ]]; then
|
||||||
|
echo "🔗 공통 skill 을 $SKILLS_DEST 로 링크합니다..."
|
||||||
|
mkdir -p "$SKILLS_DEST"
|
||||||
|
for dir in "$SKILLS_SRC"/*/; do
|
||||||
|
[[ -d "$dir" ]] || continue
|
||||||
|
name="$(basename "$dir")"
|
||||||
|
[[ "$name" == "README"* ]] && continue
|
||||||
|
link_src="../common/skills/$name"
|
||||||
|
link_dest="$SKILLS_DEST/$name"
|
||||||
|
if [[ -L "$link_dest" ]]; then
|
||||||
|
echo " ⏭ $link_dest (이미 링크됨)"
|
||||||
|
elif [[ -e "$link_dest" ]]; then
|
||||||
|
echo " ⚠️ $link_dest (실제 파일/폴더 존재 - 건너뜀. link-skills.sh --force 로 덮어쓰기)"
|
||||||
|
else
|
||||||
|
ln -s "$link_src" "$link_dest"
|
||||||
|
echo " ✅ $link_dest → $link_src"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 설치가 완료되었습니다."
|
||||||
|
echo " - 공통 지침: $TARGET_PATH/CLAUDE.md"
|
||||||
|
echo " - 프로젝트 지침: $PROJECT_PATH/"
|
||||||
|
echo " - 공통 skill: $SKILLS_DEST/ (submodule 에 심볼릭 링크)"
|
||||||
|
echo " - 엔트리 파일: CLAUDE.md"
|
||||||
|
echo ""
|
||||||
|
echo "다음 작업을 진행해 주세요:"
|
||||||
|
echo " 1) $PROJECT_PATH/*.md 내용을 프로젝트에 맞게 채우기"
|
||||||
|
echo " 2) 변경 사항을 커밋하기"
|
||||||
|
echo " git add .gitmodules .claude CLAUDE.md"
|
||||||
|
echo " git commit -m 'chore: add fe-common-rules submodule'"
|
||||||
139
scripts/link-skills.sh
Executable file
139
scripts/link-skills.sh
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# fe-common-rules skill linker
|
||||||
|
# 공통 저장소의 skills/* 를 프로젝트의 .claude/skills/* 로 심볼릭 링크합니다.
|
||||||
|
# 심볼릭 링크이므로 submodule 업데이트 시 skill 도 자동으로 최신 버전이 됩니다.
|
||||||
|
#
|
||||||
|
# 사용법:
|
||||||
|
# bash .claude/common/scripts/link-skills.sh # 모든 skill 링크
|
||||||
|
# bash .claude/common/scripts/link-skills.sh <skill-name> # 특정 skill 만
|
||||||
|
# bash .claude/common/scripts/link-skills.sh --dry-run # 실제 링크 없이 미리보기
|
||||||
|
# bash .claude/common/scripts/link-skills.sh --force # 기존 링크/폴더 덮어쓰기
|
||||||
|
# bash .claude/common/scripts/link-skills.sh --unlink # 공통 skill 링크 제거
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
COMMON_PATH=".claude/common"
|
||||||
|
SKILLS_SRC="$COMMON_PATH/skills"
|
||||||
|
SKILLS_DEST=".claude/skills"
|
||||||
|
|
||||||
|
MODE="safe" # safe | force | dry-run | unlink
|
||||||
|
TARGET=""
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) MODE="dry-run" ;;
|
||||||
|
--force) MODE="force" ;;
|
||||||
|
--unlink) MODE="unlink" ;;
|
||||||
|
-h|--help)
|
||||||
|
grep '^#' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "❌ 알 수 없는 옵션: $arg" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
TARGET="$arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
if [[ ! -d "$SKILLS_SRC" ]]; then
|
||||||
|
echo "❌ $SKILLS_SRC 가 없습니다. 먼저 submodule 을 설치/업데이트하세요:" >&2
|
||||||
|
echo " git submodule update --init --recursive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$SKILLS_DEST"
|
||||||
|
|
||||||
|
# 링크 대상 결정
|
||||||
|
declare -a skills
|
||||||
|
if [[ -n "$TARGET" ]]; then
|
||||||
|
if [[ ! -d "$SKILLS_SRC/$TARGET" ]]; then
|
||||||
|
echo "❌ '$TARGET' skill 을 $SKILLS_SRC 에서 찾지 못했습니다." >&2
|
||||||
|
echo " 사용 가능한 skill:" >&2
|
||||||
|
ls -1 "$SKILLS_SRC" 2>/dev/null | grep -v '^README' | sed 's/^/ - /' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
skills=("$TARGET")
|
||||||
|
else
|
||||||
|
shopt -s nullglob
|
||||||
|
for dir in "$SKILLS_SRC"/*/; do
|
||||||
|
name="$(basename "$dir")"
|
||||||
|
# README 같은 파일은 이미 걸러지지만 추가 보호
|
||||||
|
[[ "$name" == "README"* ]] && continue
|
||||||
|
skills+=("$name")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#skills[@]} -eq 0 ]]; then
|
||||||
|
echo "ℹ️ 링크할 skill 이 없습니다."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔗 공통 skill 링크 (mode=$MODE)"
|
||||||
|
echo " source: $SKILLS_SRC"
|
||||||
|
echo " dest: $SKILLS_DEST"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
linked=0
|
||||||
|
skipped=0
|
||||||
|
removed=0
|
||||||
|
|
||||||
|
for name in "${skills[@]}"; do
|
||||||
|
src="../common/skills/$name" # 심볼릭 링크의 상대 경로 (.claude/skills 기준)
|
||||||
|
dest="$SKILLS_DEST/$name"
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
unlink)
|
||||||
|
if [[ -L "$dest" ]]; then
|
||||||
|
rm "$dest"
|
||||||
|
echo " 🗑 $dest (링크 제거)"
|
||||||
|
removed=$((removed + 1))
|
||||||
|
else
|
||||||
|
echo " ⏭ $dest (링크 아님 - 건너뜀)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
dry-run)
|
||||||
|
if [[ -e "$dest" || -L "$dest" ]]; then
|
||||||
|
echo " ⏭ $dest (이미 존재)"
|
||||||
|
else
|
||||||
|
echo " ➕ ln -s $src $dest"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
force)
|
||||||
|
rm -rf "$dest"
|
||||||
|
ln -s "$src" "$dest"
|
||||||
|
echo " ✅ $dest → $src (덮어씀)"
|
||||||
|
linked=$((linked + 1))
|
||||||
|
;;
|
||||||
|
safe)
|
||||||
|
if [[ -L "$dest" ]]; then
|
||||||
|
echo " ⏭ $dest (이미 링크됨)"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
elif [[ -e "$dest" ]]; then
|
||||||
|
echo " ⚠️ $dest (실제 파일/폴더가 존재. --force 로 덮어쓰세요)"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
else
|
||||||
|
ln -s "$src" "$dest"
|
||||||
|
echo " ✅ $dest → $src"
|
||||||
|
linked=$((linked + 1))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
case "$MODE" in
|
||||||
|
unlink) echo "🎉 제거 완료: $removed 개 링크 제거됨";;
|
||||||
|
dry-run) echo "🔍 미리보기 완료 (실제 변경 없음)";;
|
||||||
|
*) echo "🎉 완료: 링크 $linked개 / 건너뜀 $skipped개";;
|
||||||
|
esac
|
||||||
36
scripts/update.sh
Executable file
36
scripts/update.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# fe-common-rules updater
|
||||||
|
# 현재 프로젝트에 설치된 .claude/common submodule 을 최신 버전으로 갱신합니다.
|
||||||
|
#
|
||||||
|
# 사용법:
|
||||||
|
# bash .claude/common/scripts/update.sh
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TARGET_PATH=".claude/common"
|
||||||
|
|
||||||
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$TARGET_PATH" ]]; then
|
||||||
|
echo "❌ '$TARGET_PATH' 가 존재하지 않습니다. 먼저 install.sh 로 설치하세요." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔄 fe-common-rules 를 최신 버전으로 업데이트합니다..."
|
||||||
|
git submodule update --remote --merge "$TARGET_PATH"
|
||||||
|
|
||||||
|
# 변경 사항 확인
|
||||||
|
if git diff --quiet -- "$TARGET_PATH"; then
|
||||||
|
echo "✅ 이미 최신 상태입니다."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ 업데이트가 완료되었습니다. 변경된 submodule 포인터를 커밋하세요:"
|
||||||
|
echo ""
|
||||||
|
echo " git add $TARGET_PATH"
|
||||||
|
echo " git commit -m 'chore: update fe-common-rules submodule'"
|
||||||
52
skills/README.md
Normal file
52
skills/README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Common Skills
|
||||||
|
|
||||||
|
이 폴더는 팀 공통으로 사용할 Claude **skill** 들을 모아두는 공간입니다.
|
||||||
|
각 프로젝트는 submodule 로 이 저장소를 가져온 뒤, `.claude/common/skills/*` 를
|
||||||
|
`.claude/skills/*` 로 **심볼릭 링크**하여 사용합니다.
|
||||||
|
|
||||||
|
## Skill 구조
|
||||||
|
|
||||||
|
각 skill 은 하나의 폴더이며, 루트에 `SKILL.md` 를 가집니다.
|
||||||
|
|
||||||
|
```
|
||||||
|
skills/
|
||||||
|
└── <skill-name>/
|
||||||
|
├── SKILL.md # YAML frontmatter (name, description) + 지시문
|
||||||
|
└── (선택) 추가 자료 — 예시 파일, 템플릿, 스크립트 등
|
||||||
|
```
|
||||||
|
|
||||||
|
`SKILL.md` 예시:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: <skill-name>
|
||||||
|
description: <Claude 가 이 skill 을 언제 써야 하는지 한 줄로>
|
||||||
|
---
|
||||||
|
|
||||||
|
# <스킬 제목>
|
||||||
|
|
||||||
|
## 언제 사용하는가
|
||||||
|
...
|
||||||
|
|
||||||
|
## 작업 순서
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 프로젝트에 연결하기
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 공통 저장소의 skill 을 .claude/skills/ 로 전부 링크
|
||||||
|
bash .claude/common/scripts/link-skills.sh
|
||||||
|
|
||||||
|
# 차이 확인
|
||||||
|
bash .claude/common/scripts/link-skills.sh --dry-run
|
||||||
|
|
||||||
|
# 특정 skill 만 링크
|
||||||
|
bash .claude/common/scripts/link-skills.sh vue-component-review
|
||||||
|
```
|
||||||
|
|
||||||
|
## 새 skill 추가 프로세스
|
||||||
|
|
||||||
|
1. 이 저장소에서 `skills/<skill-name>/SKILL.md` 를 작성
|
||||||
|
2. PR 을 올리고 팀 리뷰
|
||||||
|
3. 머지 후 각 프로젝트에서 `git submodule update --remote` → `link-skills.sh` 실행
|
||||||
78
skills/conventional-commit/SKILL.md
Normal file
78
skills/conventional-commit/SKILL.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
name: conventional-commit
|
||||||
|
description: Git 변경사항을 팀의 Conventional Commits 규칙(fe-common-rules/rules/commit-pr.md)에 맞춰 커밋 메시지로 작성할 때 사용합니다. 사용자가 "커밋 메시지 만들어줘", "commit", "커밋해줘" 등을 요청하면 트리거됩니다.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Conventional Commit 작성
|
||||||
|
|
||||||
|
이 skill 은 `git diff --staged` / `git status` 결과를 바탕으로 팀 규칙에 맞는 커밋
|
||||||
|
메시지를 작성합니다.
|
||||||
|
|
||||||
|
## 팀 규칙 요약
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### type
|
||||||
|
|
||||||
|
- `feat`: 새로운 기능
|
||||||
|
- `fix`: 버그 수정
|
||||||
|
- `refactor`: 기능 변화 없는 구조 개선
|
||||||
|
- `style`: 포맷/세미콜론 등 스타일
|
||||||
|
- `docs`: 문서
|
||||||
|
- `test`: 테스트 추가/수정
|
||||||
|
- `chore`: 빌드, 설정, 패키지
|
||||||
|
- `perf`: 성능 개선
|
||||||
|
- `ci`: CI 설정
|
||||||
|
|
||||||
|
### subject
|
||||||
|
|
||||||
|
- 50자 이내
|
||||||
|
- 명령형 현재 시제 (`add`, `fix` — `added`/`fixes` 아님)
|
||||||
|
- 끝에 마침표 금지
|
||||||
|
- 영어 소문자 시작
|
||||||
|
|
||||||
|
### body
|
||||||
|
|
||||||
|
- "무엇을"보다 **"왜"** 를 설명
|
||||||
|
- 한 줄 72자 이내
|
||||||
|
- 한 커밋 = 한 논리적 변경
|
||||||
|
|
||||||
|
## 작업 순서
|
||||||
|
|
||||||
|
1. **상태 확인**
|
||||||
|
- `git status` 로 staged/unstaged 파일 파악
|
||||||
|
- staged 변경이 없으면 사용자에게 먼저 `git add` 를 하라고 안내
|
||||||
|
2. **변경 분석**
|
||||||
|
- `git diff --staged` 로 실제 변경 내용 확인
|
||||||
|
- 변경을 한 문장으로 요약 (type + scope + subject 를 정하는 근거)
|
||||||
|
3. **메시지 작성**
|
||||||
|
- subject 는 50자 이내, 영문 명령형
|
||||||
|
- body 는 한국어로 "왜" 중심 설명 (한 줄 72자 제한)
|
||||||
|
- 이슈 번호가 있으면 `Refs: #123` footer 추가
|
||||||
|
4. **사용자 확인**
|
||||||
|
- 작성한 메시지를 보여주고 커밋 실행 여부를 물음
|
||||||
|
- 사용자가 명시적으로 "커밋해" 라고 할 때만 실제 `git commit` 실행
|
||||||
|
|
||||||
|
## 출력 예시
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(user): add profile image upload
|
||||||
|
|
||||||
|
프로필 이미지 업로드 요구사항에 따라 multipart 업로드 경로를 추가했습니다.
|
||||||
|
기존 텍스트 필드 업데이트 API 는 변경하지 않았습니다.
|
||||||
|
|
||||||
|
Refs: #123
|
||||||
|
```
|
||||||
|
|
||||||
|
## 주의사항
|
||||||
|
|
||||||
|
- **논리적으로 섞인 변경**이 감지되면(예: feat + refactor 가 같이 있음) 커밋 분리를 제안한다.
|
||||||
|
- `package.json` / lock 파일이 함께 staged 되어 있으면 의존성 추가 사유를 body 에 명시한다.
|
||||||
|
- 사용자가 커밋 실행을 명시적으로 허락하기 전까지 `git commit` 명령을 직접 실행하지 않는다.
|
||||||
|
- Co-authored-by 등 footer 는 사용자가 요청할 때만 추가한다.
|
||||||
310
skills/edm-email-html/SKILL.md
Normal file
310
skills/edm-email-html/SKILL.md
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
---
|
||||||
|
name: edm-email-html
|
||||||
|
description: |
|
||||||
|
EDM(이메일 다이렉트 마케팅) HTML을 구현하는 전체 워크플로우 스킬.
|
||||||
|
Figma 디자인 → HTML table 마크업 → 아웃룩 호환 → 검수까지 단계별 가이드를 제공합니다.
|
||||||
|
|
||||||
|
다음 상황에서 반드시 사용하세요:
|
||||||
|
- "EDM 만들어줘", "이메일 템플릿 구현", "뉴스레터 HTML"
|
||||||
|
- "아웃룩에서 깨지는 이메일 수정", "이메일 HTML 마크업"
|
||||||
|
- Figma 디자인을 받고 이메일 HTML로 변환할 때
|
||||||
|
- "메일 발송용 HTML", "eDM 퍼블리싱", "HTML 이메일"
|
||||||
|
- 이메일 클라이언트 호환성 문제가 있을 때
|
||||||
|
---
|
||||||
|
|
||||||
|
# EDM HTML 구현 가이드
|
||||||
|
|
||||||
|
이메일 HTML은 일반 웹과 다른 세계입니다. 2000년대 테이블 코딩이 아직도 정답이며, Flexbox와 Grid는 쓸 수 없습니다. 이 스킬은 Figma 디자인에서 시작해 모든 이메일 클라이언트에서 깨지지 않는 HTML을 만드는 과정을 안내합니다.
|
||||||
|
|
||||||
|
## 워크플로우
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Figma 디자인 파악 → 2. HTML 마크업 → 3. 아웃룩 호환 → 4. 검수
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Figma 디자인 파악
|
||||||
|
|
||||||
|
### Figma MCP 사용 가능 시
|
||||||
|
Claude Code에 Figma MCP가 설정되어 있다면 Figma URL로 직접 디자인 데이터를 읽을 수 있습니다. MCP가 연결되어 있는지 먼저 확인하고, 가능하다면 자동 추출을 시도하세요.
|
||||||
|
|
||||||
|
추출 가능한 속성:
|
||||||
|
- 컬러 HEX값 (RGBA → HEX 자동 변환)
|
||||||
|
- 폰트 패밀리, 사이즈(px), 굵기, 줄간격
|
||||||
|
- 레이아웃 치수: 너비, 높이, padding, 섹션 간격
|
||||||
|
- 이미지 에셋 URL (CDN 업로드 필요)
|
||||||
|
- CTA 링크 (레이어 설명 필드에서 추출)
|
||||||
|
|
||||||
|
### Figma MCP 없이 진행 시
|
||||||
|
사용자에게 다음 정보를 요청하거나 스크린샷으로 파악하세요.
|
||||||
|
|
||||||
|
**필수 확인 항목:**
|
||||||
|
- 전체 이메일 너비 (권장: **600px**)
|
||||||
|
- 각 섹션 배경색, 텍스트 색상 (HEX)
|
||||||
|
- 폰트: 패밀리, 사이즈(px), 굵기, 줄간격
|
||||||
|
- 이미지: 가로×세로(px)
|
||||||
|
- 여백: 섹션 간 간격, 좌우 패딩
|
||||||
|
- CTA 버튼: 크기, 색상, 텍스트, 링크 URL
|
||||||
|
- 푸터: 회사 정보, 수신거부 링크
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: HTML table 마크업
|
||||||
|
|
||||||
|
### 절대 원칙
|
||||||
|
|
||||||
|
이메일 HTML에서 반드시 지켜야 하는 규칙들입니다. 이 규칙을 어기면 특정 클라이언트에서 레이아웃이 무너집니다:
|
||||||
|
|
||||||
|
| 규칙 | 이유 |
|
||||||
|
|------|------|
|
||||||
|
| `table`, `tr`, `td`만 레이아웃에 사용 | div는 Outlook 등에서 무시됨 |
|
||||||
|
| inline CSS 우선 | Gmail이 `<head>` style 태그를 제거함 |
|
||||||
|
| `width`/`height` 속성 필수 | CSS만으론 Outlook이 무시함 |
|
||||||
|
| `margin` 사용 금지 | 빈 `<tr>`행이나 `padding`으로 대체 |
|
||||||
|
| `padding` 개별 속성 사용 | 단축 속성(`padding: 10px 20px`)은 일부 클라이언트 미지원 |
|
||||||
|
| 모든 `<table>`에 `cellpadding="0" cellspacing="0" border="0"` | 브라우저 기본 스타일 초기화 |
|
||||||
|
|
||||||
|
### 기본 템플릿
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="format-detection" content="telephone=no, date=no, address=no">
|
||||||
|
<title>이메일 제목</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body { margin: 0; padding: 0; width: 100%; background-color: #f5f5f5; }
|
||||||
|
table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
||||||
|
img { display: block; border: 0; outline: none; text-decoration: none; }
|
||||||
|
|
||||||
|
/* 미디어쿼리는 여기서만 (Outlook은 무시하지만 Gmail/Apple Mail에서 적용) */
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.mobile-full { width: 100% !important; display: block !important; }
|
||||||
|
.mobile-padding { padding-left: 16px !important; padding-right: 16px !important; }
|
||||||
|
.mobile-center { text-align: center !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; background-color: #f5f5f5;">
|
||||||
|
|
||||||
|
<!-- 외부 래퍼: 배경색과 수평 중앙 정렬 -->
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f5f5f5">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding-top: 20px; padding-bottom: 20px;">
|
||||||
|
|
||||||
|
<!-- 600px 컨테이너 -->
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="width: 600px; max-width: 100%; background-color: #ffffff;">
|
||||||
|
|
||||||
|
<!-- 헤더 -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top: 0;">
|
||||||
|
<!-- 로고 이미지 등 -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 본문 -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top: 30px; padding-bottom: 30px;
|
||||||
|
padding-left: 30px; padding-right: 30px;">
|
||||||
|
<!-- 메인 콘텐츠 -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 푸터 -->
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#f5f5f5"
|
||||||
|
style="background-color: #f5f5f5;
|
||||||
|
padding-top: 20px; padding-bottom: 20px;
|
||||||
|
padding-left: 20px; padding-right: 20px;
|
||||||
|
text-align: center;">
|
||||||
|
<!-- 회사 정보 + 수신거부 링크 (필수) -->
|
||||||
|
<p style="font-family: Arial, sans-serif; font-size: 12px;
|
||||||
|
color: #999999; margin: 0; line-height: 1.5;">
|
||||||
|
회사명 | 주소<br>
|
||||||
|
<a href="[수신거부URL]"
|
||||||
|
style="color: #999999; text-decoration: underline;">
|
||||||
|
수신거부
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 안전한 폰트
|
||||||
|
|
||||||
|
웹폰트(`@font-face`, Google Fonts)는 대부분의 이메일 클라이언트에서 지원하지 않습니다. Pretendard, Noto Sans KR 같은 폰트를 Figma에서 사용했어도 이메일에서는 안전 폰트로 대체해야 합니다.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 권장 스택 (한국어 이메일) */
|
||||||
|
font-family: -apple-system, Arial, 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
|
||||||
|
/* Outlook 전용 (MSO 조건부 주석 내) */
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: 아웃룩 호환성
|
||||||
|
|
||||||
|
아웃룩 2007~2019는 Word 엔진으로 이메일을 렌더링해서 현대 CSS를 거의 무시합니다. MSO 조건부 주석으로 아웃룩과 그 외 클라이언트를 분리해서 처리하세요.
|
||||||
|
|
||||||
|
### MSO 조건부 주석
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!--[if mso]>
|
||||||
|
<!-- 아웃룩에서만 렌더링 -->
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<!-- 아웃룩 제외 클라이언트에서 렌더링 -->
|
||||||
|
<!--<![endif]-->
|
||||||
|
```
|
||||||
|
|
||||||
|
### 아웃룩이 무시하는 주요 속성
|
||||||
|
|
||||||
|
| CSS 속성 | 아웃룩 동작 | 대체 방법 |
|
||||||
|
|----------|-----------|---------|
|
||||||
|
| `background-image` | 미지원 | `<img>` 태그 직접 사용 |
|
||||||
|
| `border-radius` | 무시 | VML 사용 또는 이미지 버튼 |
|
||||||
|
| `margin` | 무시 | `padding` 또는 빈 `<tr>` 행 |
|
||||||
|
| `box-shadow` | 무시 | 포기 또는 이미지로 대체 |
|
||||||
|
| `@media query` | 2007/2010 미지원 | 테이블 고정폭으로 데스크톱 설계 |
|
||||||
|
|
||||||
|
### VML 버튼 (반드시 사용)
|
||||||
|
|
||||||
|
아웃룩에서 CSS 버튼은 배경색 없는 텍스트 링크로 표시됩니다. CTA 버튼은 항상 VML을 포함하세요:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div style="text-align: center;
|
||||||
|
padding-top: 20px; padding-bottom: 20px;">
|
||||||
|
|
||||||
|
<!--[if mso]>
|
||||||
|
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:w="urn:schemas-microsoft-com:office:word"
|
||||||
|
href="https://example.com"
|
||||||
|
style="height: 44px; v-text-anchor: middle; width: 200px;"
|
||||||
|
arcsize="5%"
|
||||||
|
stroke="f"
|
||||||
|
fillcolor="#FF6B6B">
|
||||||
|
<w:anchorlock/>
|
||||||
|
<center style="color: #ffffff; font-family: Arial, sans-serif;
|
||||||
|
font-size: 16px; font-weight: bold;">
|
||||||
|
지금 확인하기
|
||||||
|
</center>
|
||||||
|
</v:roundrect>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<a href="https://example.com"
|
||||||
|
style="background-color: #FF6B6B;
|
||||||
|
color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 12px; padding-bottom: 12px;
|
||||||
|
padding-left: 30px; padding-right: 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;">
|
||||||
|
지금 확인하기
|
||||||
|
</a>
|
||||||
|
<!--<![endif]-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 이미지 처리
|
||||||
|
|
||||||
|
이미지 차단 시에도 레이아웃이 깨지지 않도록 `alt` 텍스트와 배경색을 함께 지정하세요:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<td bgcolor="#FF6B6B" style="background-color: #FF6B6B;">
|
||||||
|
<img src="https://cdn.example.com/banner.jpg"
|
||||||
|
alt="7월 여름 세일 최대 50% 할인"
|
||||||
|
width="600"
|
||||||
|
height="300"
|
||||||
|
style="display: block; width: 100%; max-width: 600px;
|
||||||
|
height: auto; border: 0;">
|
||||||
|
</td>
|
||||||
|
```
|
||||||
|
|
||||||
|
이미지는 반드시 `https://` CDN 절대 경로를 사용하세요. 로컬 경로나 상대 경로는 이메일에서 작동하지 않습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: 검수 체크리스트
|
||||||
|
|
||||||
|
### 코드 구조 (필수)
|
||||||
|
- [ ] 모든 `<table>`에 `cellpadding="0" cellspacing="0" border="0"`
|
||||||
|
- [ ] 모든 `<img>`에 `width`, `height`, `alt` 속성
|
||||||
|
- [ ] `margin` 미사용 (padding 또는 빈 `<tr>` 행으로 대체)
|
||||||
|
- [ ] `padding` 단축 속성 제거 (개별 속성 사용)
|
||||||
|
- [ ] CTA 버튼에 VML 코드 포함
|
||||||
|
- [ ] 이미지 `src`가 HTTPS 절대 URL
|
||||||
|
|
||||||
|
### 콘텐츠 (필수)
|
||||||
|
- [ ] 푸터에 수신거부 링크 포함
|
||||||
|
- [ ] 모든 링크 href 유효성 확인
|
||||||
|
- [ ] 이미지 alt 텍스트 의미있게 작성 (장식용이면 `alt=""`)
|
||||||
|
|
||||||
|
### Figma 디자인 대비 검수
|
||||||
|
- [ ] 전체 너비 600px
|
||||||
|
- [ ] 색상 HEX값 일치
|
||||||
|
- [ ] 폰트 사이즈, 굵기 일치
|
||||||
|
- [ ] 버튼 크기, 색상 일치
|
||||||
|
- [ ] 섹션 간 여백 일치
|
||||||
|
|
||||||
|
### 테스트 도구
|
||||||
|
|
||||||
|
| 도구 | 용도 | 비용 |
|
||||||
|
|------|------|------|
|
||||||
|
| [Litmus](https://www.litmus.com) | 100+ 클라이언트 렌더링 미리보기 | 유료 |
|
||||||
|
| [Email on Acid](https://www.emailonacid.com) | 크로스 클라이언트 + 접근성 감사 | 유료 |
|
||||||
|
| [Mailtrap](https://mailtrap.io) | 개발 환경 샌드박스, 스팸 점수 | 무료 플랜 |
|
||||||
|
| [SpamTest.io](https://spamtest.io/) | 스팸 점수, SPF/DKIM/DMARC 확인 | 무료 |
|
||||||
|
|
||||||
|
**최소 테스트 클라이언트:** Gmail 웹, Outlook (Windows), Apple Mail, 모바일 Gmail
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2컬럼 레이아웃 예시
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 데스크톱: 2열 | 모바일: 스택 -->
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td width="280" valign="top"
|
||||||
|
style="width: 280px; padding-right: 20px;"
|
||||||
|
class="mobile-full">
|
||||||
|
<!-- 왼쪽 -->
|
||||||
|
</td>
|
||||||
|
<td width="280" valign="top"
|
||||||
|
style="width: 280px; padding-left: 20px;"
|
||||||
|
class="mobile-full">
|
||||||
|
<!-- 오른쪽 -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 참고 자료
|
||||||
|
|
||||||
|
상세 내용은 references 폴더를 참조하세요:
|
||||||
|
- `references/html-patterns.md` — 헤더/푸터/버튼/이미지 완성 코드 패턴
|
||||||
|
- `references/verification-checklist.md` — 전체 검수 체크리스트 (시각적/기능/스팸)
|
||||||
24
skills/edm-email-html/assets/example_asset.txt
Normal file
24
skills/edm-email-html/assets/example_asset.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Example Asset File
|
||||||
|
|
||||||
|
This placeholder represents where asset files would be stored.
|
||||||
|
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
|
||||||
|
|
||||||
|
Asset files are NOT intended to be loaded into context, but rather used within
|
||||||
|
the output Claude produces.
|
||||||
|
|
||||||
|
Example asset files from other skills:
|
||||||
|
- Brand guidelines: logo.png, slides_template.pptx
|
||||||
|
- Frontend builder: hello-world/ directory with HTML/React boilerplate
|
||||||
|
- Typography: custom-font.ttf, font-family.woff2
|
||||||
|
- Data: sample_data.csv, test_dataset.json
|
||||||
|
|
||||||
|
## Common Asset Types
|
||||||
|
|
||||||
|
- Templates: .pptx, .docx, boilerplate directories
|
||||||
|
- Images: .png, .jpg, .svg, .gif
|
||||||
|
- Fonts: .ttf, .otf, .woff, .woff2
|
||||||
|
- Boilerplate code: Project directories, starter files
|
||||||
|
- Icons: .ico, .svg
|
||||||
|
- Data files: .csv, .json, .xml, .yaml
|
||||||
|
|
||||||
|
Note: This is a text placeholder. Actual assets can be any file type.
|
||||||
34
skills/edm-email-html/references/api_reference.md
Normal file
34
skills/edm-email-html/references/api_reference.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Reference Documentation for Edm Email Html
|
||||||
|
|
||||||
|
This is a placeholder for detailed reference documentation.
|
||||||
|
Replace with actual reference content or delete if not needed.
|
||||||
|
|
||||||
|
Example real reference docs from other skills:
|
||||||
|
- product-management/references/communication.md - Comprehensive guide for status updates
|
||||||
|
- product-management/references/context_building.md - Deep-dive on gathering context
|
||||||
|
- bigquery/references/ - API references and query examples
|
||||||
|
|
||||||
|
## When Reference Docs Are Useful
|
||||||
|
|
||||||
|
Reference docs are ideal for:
|
||||||
|
- Comprehensive API documentation
|
||||||
|
- Detailed workflow guides
|
||||||
|
- Complex multi-step processes
|
||||||
|
- Information too lengthy for main SKILL.md
|
||||||
|
- Content that's only needed for specific use cases
|
||||||
|
|
||||||
|
## Structure Suggestions
|
||||||
|
|
||||||
|
### API Reference Example
|
||||||
|
- Overview
|
||||||
|
- Authentication
|
||||||
|
- Endpoints with examples
|
||||||
|
- Error codes
|
||||||
|
- Rate limits
|
||||||
|
|
||||||
|
### Workflow Guide Example
|
||||||
|
- Prerequisites
|
||||||
|
- Step-by-step instructions
|
||||||
|
- Common patterns
|
||||||
|
- Troubleshooting
|
||||||
|
- Best practices
|
||||||
327
skills/edm-email-html/references/html-patterns.md
Normal file
327
skills/edm-email-html/references/html-patterns.md
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
# EDM HTML 코드 패턴 모음
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1컬럼 레이아웃 (전체 템플릿)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="format-detection" content="telephone=no, date=no, address=no">
|
||||||
|
<title>이메일 제목</title>
|
||||||
|
<!--[if mso]>
|
||||||
|
<style type="text/css">
|
||||||
|
body, table, td, p, a { font-family: Arial, sans-serif !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<style type="text/css">
|
||||||
|
body { margin: 0; padding: 0; width: 100%; background-color: #f5f5f5; }
|
||||||
|
table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
||||||
|
img { display: block; border: 0; outline: none; text-decoration: none; }
|
||||||
|
a { color: inherit; }
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.container { width: 100% !important; }
|
||||||
|
.mobile-full { width: 100% !important; display: block !important; }
|
||||||
|
.mobile-padding { padding-left: 16px !important; padding-right: 16px !important; }
|
||||||
|
.mobile-center { text-align: center !important; }
|
||||||
|
.mobile-img { width: 100% !important; height: auto !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; background-color: #f5f5f5;">
|
||||||
|
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f5f5f5">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding-top: 20px; padding-bottom: 20px;">
|
||||||
|
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
class="container"
|
||||||
|
style="width: 600px; max-width: 100%; background-color: #ffffff;">
|
||||||
|
|
||||||
|
<!-- 헤더 -->
|
||||||
|
<tr>
|
||||||
|
<td align="center"
|
||||||
|
style="padding-top: 24px; padding-bottom: 24px;
|
||||||
|
padding-left: 30px; padding-right: 30px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;">
|
||||||
|
<img src="https://cdn.example.com/logo.png"
|
||||||
|
alt="회사 로고" width="120" height="40"
|
||||||
|
style="display: block; border: 0;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 히어로 이미지 -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0; line-height: 0;">
|
||||||
|
<img src="https://cdn.example.com/hero.jpg"
|
||||||
|
alt="이벤트 배너"
|
||||||
|
width="600" height="280"
|
||||||
|
class="mobile-img"
|
||||||
|
style="display: block; width: 100%; max-width: 600px;
|
||||||
|
height: auto; border: 0;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 본문 -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top: 32px; padding-bottom: 32px;
|
||||||
|
padding-left: 32px; padding-right: 32px;"
|
||||||
|
class="mobile-padding">
|
||||||
|
<h1 style="font-family: Arial, sans-serif;
|
||||||
|
font-size: 24px; font-weight: bold;
|
||||||
|
color: #111827; line-height: 1.3;
|
||||||
|
margin: 0 0 16px 0;">
|
||||||
|
이메일 제목이 여기 들어갑니다
|
||||||
|
</h1>
|
||||||
|
<p style="font-family: Arial, sans-serif;
|
||||||
|
font-size: 15px; color: #374151;
|
||||||
|
line-height: 1.7;
|
||||||
|
margin: 0 0 24px 0;">
|
||||||
|
본문 내용이 여기 들어갑니다. 가독성을 위해 line-height를
|
||||||
|
1.5 이상으로 설정하는 것이 좋습니다.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- CTA 버튼 -->
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<!--[if mso]>
|
||||||
|
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:w="urn:schemas-microsoft-com:office:word"
|
||||||
|
href="https://example.com"
|
||||||
|
style="height: 48px; v-text-anchor: middle; width: 200px;"
|
||||||
|
arcsize="8%" stroke="f" fillcolor="#1a56db">
|
||||||
|
<w:anchorlock/>
|
||||||
|
<center style="color: #ffffff; font-family: Arial, sans-serif;
|
||||||
|
font-size: 16px; font-weight: bold;">
|
||||||
|
자세히 보기
|
||||||
|
</center>
|
||||||
|
</v:roundrect>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<a href="https://example.com"
|
||||||
|
style="background-color: #1a56db; color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 14px; padding-bottom: 14px;
|
||||||
|
padding-left: 32px; padding-right: 32px;
|
||||||
|
text-decoration: none; border-radius: 6px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 16px; font-weight: bold;">
|
||||||
|
자세히 보기
|
||||||
|
</a>
|
||||||
|
<!--<![endif]-->
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 푸터 -->
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#f9fafb"
|
||||||
|
style="background-color: #f9fafb;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
padding-top: 24px; padding-bottom: 24px;
|
||||||
|
padding-left: 32px; padding-right: 32px;
|
||||||
|
text-align: center;">
|
||||||
|
<p style="font-family: Arial, sans-serif; font-size: 12px;
|
||||||
|
color: #9ca3af; line-height: 1.6; margin: 0 0 8px 0;">
|
||||||
|
<strong>회사명</strong> | 서울시 강남구 테헤란로 123
|
||||||
|
</p>
|
||||||
|
<p style="font-family: Arial, sans-serif; font-size: 12px;
|
||||||
|
color: #9ca3af; line-height: 1.6; margin: 0;">
|
||||||
|
<a href="https://example.com/unsubscribe"
|
||||||
|
style="color: #9ca3af; text-decoration: underline;">
|
||||||
|
수신거부
|
||||||
|
</a>
|
||||||
|
|
|
||||||
|
<a href="https://example.com/privacy"
|
||||||
|
style="color: #9ca3af; text-decoration: underline;">
|
||||||
|
개인정보처리방침
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2컬럼 이미지 + 텍스트
|
||||||
|
|
||||||
|
```html
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<!-- 이미지 열 (40%) -->
|
||||||
|
<td width="220" valign="top"
|
||||||
|
style="width: 220px; padding-right: 0;"
|
||||||
|
class="mobile-full">
|
||||||
|
<img src="https://cdn.example.com/product.jpg"
|
||||||
|
alt="상품명" width="220" height="220"
|
||||||
|
class="mobile-img"
|
||||||
|
style="display: block; width: 100%; height: auto; border: 0;">
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- 간격 -->
|
||||||
|
<td width="20" style="width: 20px; min-width: 20px;"> </td>
|
||||||
|
|
||||||
|
<!-- 텍스트 열 (60%) -->
|
||||||
|
<td width="360" valign="top"
|
||||||
|
style="width: 360px; padding-top: 8px;"
|
||||||
|
class="mobile-full mobile-padding">
|
||||||
|
<h2 style="font-family: Arial, sans-serif;
|
||||||
|
font-size: 18px; font-weight: bold;
|
||||||
|
color: #111827; margin: 0 0 8px 0;
|
||||||
|
line-height: 1.3;">
|
||||||
|
상품명
|
||||||
|
</h2>
|
||||||
|
<p style="font-family: Arial, sans-serif; font-size: 14px;
|
||||||
|
color: #6b7280; line-height: 1.6;
|
||||||
|
margin: 0 0 16px 0;">
|
||||||
|
상품 설명이 들어갑니다. 간결하게 핵심만 작성하세요.
|
||||||
|
</p>
|
||||||
|
<p style="font-family: Arial, sans-serif; font-size: 20px;
|
||||||
|
font-weight: bold; color: #ef4444;
|
||||||
|
margin: 0 0 16px 0;">
|
||||||
|
₩29,900
|
||||||
|
</p>
|
||||||
|
<a href="https://example.com/product"
|
||||||
|
style="background-color: #111827; color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 10px; padding-bottom: 10px;
|
||||||
|
padding-left: 20px; padding-right: 20px;
|
||||||
|
text-decoration: none; border-radius: 4px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 13px; font-weight: bold;">
|
||||||
|
구매하기
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 헤더 배너 (이미지 기반)
|
||||||
|
|
||||||
|
이미지가 차단됐을 때도 배경색이 보이도록 `bgcolor` 속성을 함께 지정합니다:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#1a56db" style="background-color: #1a56db; line-height: 0; padding: 0;">
|
||||||
|
<img src="https://cdn.example.com/header-banner.jpg"
|
||||||
|
alt="여름 세일 최대 70% 할인"
|
||||||
|
width="600" height="240"
|
||||||
|
style="display: block; width: 100%; max-width: 600px;
|
||||||
|
height: auto; border: 0;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 섹션 구분선
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 섹션 간 여백 -->
|
||||||
|
<tr>
|
||||||
|
<td height="32" style="height: 32px; line-height: 32px;"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 수평선 -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding-left: 32px; padding-right: 32px;">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td height="1" bgcolor="#e5e7eb"
|
||||||
|
style="height: 1px; background-color: #e5e7eb; line-height: 1px;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 아웃라인(외곽선) 버튼
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!--[if mso]>
|
||||||
|
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:w="urn:schemas-microsoft-com:office:word"
|
||||||
|
href="https://example.com"
|
||||||
|
style="height: 44px; v-text-anchor: middle; width: 180px;"
|
||||||
|
arcsize="5%"
|
||||||
|
stroke="t"
|
||||||
|
strokeweight="2px"
|
||||||
|
strokecolor="#1a56db"
|
||||||
|
fillcolor="#ffffff">
|
||||||
|
<w:anchorlock/>
|
||||||
|
<center style="color: #1a56db; font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px; font-weight: bold;">
|
||||||
|
더 알아보기
|
||||||
|
</center>
|
||||||
|
</v:roundrect>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<a href="https://example.com"
|
||||||
|
style="background-color: #ffffff;
|
||||||
|
color: #1a56db;
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 12px; padding-bottom: 12px;
|
||||||
|
padding-left: 24px; padding-right: 24px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 2px solid #1a56db;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;">
|
||||||
|
더 알아보기
|
||||||
|
</a>
|
||||||
|
<!--<![endif]-->
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 소셜 아이콘 행
|
||||||
|
|
||||||
|
```html
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td style="padding-right: 8px;">
|
||||||
|
<a href="https://instagram.com/example" style="text-decoration: none;">
|
||||||
|
<img src="https://cdn.example.com/icon-instagram.png"
|
||||||
|
alt="Instagram" width="32" height="32"
|
||||||
|
style="display: block; border: 0;">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td style="padding-right: 8px;">
|
||||||
|
<a href="https://facebook.com/example" style="text-decoration: none;">
|
||||||
|
<img src="https://cdn.example.com/icon-facebook.png"
|
||||||
|
alt="Facebook" width="32" height="32"
|
||||||
|
style="display: block; border: 0;">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://youtube.com/example" style="text-decoration: none;">
|
||||||
|
<img src="https://cdn.example.com/icon-youtube.png"
|
||||||
|
alt="YouTube" width="32" height="32"
|
||||||
|
style="display: block; border: 0;">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
19
skills/edm-email-html/scripts/example.py
Executable file
19
skills/edm-email-html/scripts/example.py
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Example helper script for edm-email-html
|
||||||
|
|
||||||
|
This is a placeholder script that can be executed directly.
|
||||||
|
Replace with actual implementation or delete if not needed.
|
||||||
|
|
||||||
|
Example real scripts from other skills:
|
||||||
|
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
|
||||||
|
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("This is an example script for edm-email-html")
|
||||||
|
# TODO: Add actual script logic here
|
||||||
|
# This could be data processing, file conversion, API calls, etc.
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
87
skills/vue-component-review/SKILL.md
Normal file
87
skills/vue-component-review/SKILL.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
name: vue-component-review
|
||||||
|
description: Vue 3 / Nuxt 컴포넌트 파일을 팀 공통 지침(fe-common-rules) 기준으로 리뷰할 때 사용합니다. 사용자가 "이 컴포넌트 리뷰해줘", "컨벤션 맞는지 봐줘", "컴포넌트 체크" 등을 요청하면 트리거됩니다.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue 컴포넌트 리뷰
|
||||||
|
|
||||||
|
이 skill 은 `.vue` 파일 하나 또는 여러 개에 대해 팀 공통 지침 기준으로 리뷰 체크리스트를
|
||||||
|
수행합니다. 프로젝트 전용 컨벤션(`.claude/project/conventions.md`)이 있으면 그 규칙을
|
||||||
|
**우선**합니다.
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
|
||||||
|
### 1. 파일 구조
|
||||||
|
|
||||||
|
- [ ] `<script setup lang="ts">` 를 사용하는가? (Options API 금지)
|
||||||
|
- [ ] 파일 길이가 팀/프로젝트 제한을 넘지 않는가? (공통 200줄, 프로젝트별 오버라이드 확인)
|
||||||
|
- [ ] 하나의 컴포넌트가 단일 책임을 지키는가?
|
||||||
|
|
||||||
|
### 2. Props / Emits
|
||||||
|
|
||||||
|
- [ ] `defineProps<T>()` 제네릭 형태로 타입을 명시했는가?
|
||||||
|
- [ ] `defineEmits<{ ... }>()` 제네릭 형태로 선언했는가?
|
||||||
|
- [ ] Props 개수가 많다면 객체 props 로 묶여 있는가?
|
||||||
|
- [ ] 불리언 prop 은 `is`/`has`/`can`/`should` 로 시작하는가?
|
||||||
|
|
||||||
|
### 3. 반응성
|
||||||
|
|
||||||
|
- [ ] `ref` 와 `reactive` 를 팀 규칙대로 사용하고 있는가?
|
||||||
|
- [ ] 불필요한 `reactive` 래핑이 없는가?
|
||||||
|
- [ ] 계산된 값은 `computed` 로 뽑아냈는가?
|
||||||
|
|
||||||
|
### 4. 스타일 (Tailwind)
|
||||||
|
|
||||||
|
- [ ] 임의값 클래스(`w-[123px]`) 남용이 없는가?
|
||||||
|
- [ ] 색상/간격 토큰을 사용하는가? (임의 색상 금지)
|
||||||
|
- [ ] 조건부 클래스는 `clsx` / `cn` 로 가독성 확보되었는가?
|
||||||
|
- [ ] 클래스 순서는 `prettier-plugin-tailwindcss` 규칙을 따르는가?
|
||||||
|
|
||||||
|
### 5. 네이밍
|
||||||
|
|
||||||
|
- [ ] 파일명: `PascalCase.vue`
|
||||||
|
- [ ] 이벤트 핸들러: `handle*` 또는 `on*` 접두사
|
||||||
|
- [ ] 상수: `UPPER_SNAKE_CASE`
|
||||||
|
|
||||||
|
### 6. 의존성 / 로직
|
||||||
|
|
||||||
|
- [ ] 비즈니스 로직이 컴포넌트에 직접 박혀있지 않고 composable 로 추출되었는가?
|
||||||
|
- [ ] `$fetch` / `fetch` 직접 호출이 없는가? (프로젝트 규칙에 따라 api wrapper 사용)
|
||||||
|
- [ ] `any` 타입 사용이 없는가?
|
||||||
|
|
||||||
|
## 작업 순서
|
||||||
|
|
||||||
|
1. 리뷰 대상 파일을 읽는다. 여러 파일이면 하나씩 순차 처리한다.
|
||||||
|
2. `.claude/project/conventions.md` 가 있으면 먼저 읽고, 공통 규칙과의 차이를 기억한다.
|
||||||
|
3. 위 체크리스트를 항목별로 점검하고, 위반 사항을 발견하면 **파일명:라인번호** 와
|
||||||
|
함께 문제 요약 + 수정 예시를 제시한다.
|
||||||
|
4. 단순 포맷 이슈는 "Prettier/ESLint 로 자동 해결 가능" 이라고 덧붙인다.
|
||||||
|
5. 마지막에 우선순위별 요약(Critical / Warning / Nit)을 출력한다.
|
||||||
|
|
||||||
|
## 출력 형식
|
||||||
|
|
||||||
|
```
|
||||||
|
## 리뷰 결과: <파일명>
|
||||||
|
|
||||||
|
### 🚨 Critical (반드시 수정)
|
||||||
|
- [라인 23] Props 타입이 `any` 로 선언됨. `defineProps<{ id: string }>()` 로 변경
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### ⚠️ Warning (수정 권장)
|
||||||
|
- [라인 45] 파일 길이 210줄. 하위 컴포넌트 2개로 분리 검토
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### 💡 Nit (선택)
|
||||||
|
- [라인 12] 클래스 순서가 Tailwind 프리셋과 다름
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### ✅ 좋은 점
|
||||||
|
- 간단한 컴포넌트에 잘 맞는 단일 책임 구조
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 주의사항
|
||||||
|
|
||||||
|
- 사용자가 리팩토링을 **요청하지 않은 경우** 코드를 직접 수정하지 말고 리뷰만 수행한다.
|
||||||
|
- 프로젝트 지침과 공통 지침이 충돌하면 프로젝트 지침을 따르되, 차이를 사용자에게 알린다.
|
||||||
|
- 실제 코드 동작 변경(기능 수정)은 리뷰 범위가 아니다. 별도 작업으로 분리 제안한다.
|
||||||
9
templates/CLAUDE.md.tpl
Normal file
9
templates/CLAUDE.md.tpl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# <프로젝트 이름>
|
||||||
|
|
||||||
|
## 공통 지침
|
||||||
|
@.claude/common/CLAUDE.md
|
||||||
|
|
||||||
|
## 프로젝트 지침
|
||||||
|
@.claude/project/overview.md
|
||||||
|
@.claude/project/conventions.md
|
||||||
|
@.claude/project/architecture.md
|
||||||
46
templates/project/architecture.md
Normal file
46
templates/project/architecture.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 아키텍처
|
||||||
|
|
||||||
|
> 이 파일은 `fe-common-rules/templates/project/architecture.md` 에서 복사된 양식입니다.
|
||||||
|
> 프로젝트의 레이어 구조와 데이터 흐름을 간단히 설명해주세요.
|
||||||
|
|
||||||
|
## 레이어 구조
|
||||||
|
|
||||||
|
<프로젝트의 레이어 구조를 그림 또는 텍스트로 그려주세요>
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────────────────────────┐
|
||||||
|
│ presentation │ ← pages / components
|
||||||
|
├───────────────────────────────┤
|
||||||
|
│ logic │ ← composables / hooks / stores
|
||||||
|
├───────────────────────────────┤
|
||||||
|
│ data access │ ← api wrapper / queries
|
||||||
|
├───────────────────────────────┤
|
||||||
|
│ server │ ← 서버 라우트 / BFF
|
||||||
|
└───────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 의존 규칙
|
||||||
|
|
||||||
|
- 상위 → 하위 **단방향 의존**만 허용
|
||||||
|
- 같은 레이어 간 순환 import 금지
|
||||||
|
- <프로젝트 고유 규칙 추가>
|
||||||
|
|
||||||
|
## 데이터 흐름
|
||||||
|
|
||||||
|
1. <이벤트 발생부터 응답까지의 흐름을 간단히>
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
## 상태 관리 가이드
|
||||||
|
|
||||||
|
| 상태 종류 | 권장 위치 |
|
||||||
|
| -------------------- | ------------------------ |
|
||||||
|
| 컴포넌트 로컬 상태 | <예: ref / useState> |
|
||||||
|
| 페이지 단위 공유 상태| <예: provide/inject> |
|
||||||
|
| 앱 전역 상태 | <예: Pinia / Zustand> |
|
||||||
|
| 서버 데이터 | <예: useFetch / TanStack Query> |
|
||||||
|
|
||||||
|
## 외부 의존성
|
||||||
|
|
||||||
|
- 반드시 알아야 할 외부 서비스나 내부 API 를 나열
|
||||||
|
- 장애 발생 시 fallback 정책이 있다면 함께 기술
|
||||||
44
templates/project/conventions.md
Normal file
44
templates/project/conventions.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 프로젝트 전용 컨벤션
|
||||||
|
|
||||||
|
> 이 파일은 `fe-common-rules/templates/project/conventions.md` 에서 복사된 양식입니다.
|
||||||
|
> 공통 지침(`.claude/common/`) 외에 **이 프로젝트에서만** 적용되는 규칙을 작성하세요.
|
||||||
|
> 공통 지침과 충돌할 경우 이 문서가 우선합니다.
|
||||||
|
|
||||||
|
## 디렉토리 규칙
|
||||||
|
|
||||||
|
- `components/` — <설명>
|
||||||
|
- `composables/` 또는 `hooks/` — <설명>
|
||||||
|
- `pages/` 또는 `app/` — <설명>
|
||||||
|
- `server/` 또는 `api/` — <설명>
|
||||||
|
- `types/` — <설명>
|
||||||
|
|
||||||
|
## 컴포넌트 규칙 (공통 규칙 오버라이드)
|
||||||
|
|
||||||
|
<공통 규칙과 달리 이 프로젝트에서만 적용할 제약을 적어주세요>
|
||||||
|
|
||||||
|
- 예) 컴포넌트 파일 길이 제한: 150줄 (공통 200줄보다 엄격)
|
||||||
|
- 예) Props 개수 최대 7개, 초과 시 객체 props 로 묶기
|
||||||
|
|
||||||
|
## 스타일
|
||||||
|
|
||||||
|
- 색상/간격/타이포는 디자인 토큰만 사용하고 임의값 금지
|
||||||
|
- 다크모드 prefix: `dark:`
|
||||||
|
- 기타 프로젝트 고유 규칙: <작성>
|
||||||
|
|
||||||
|
## 네트워크 / 데이터
|
||||||
|
|
||||||
|
- API 호출 창구: <예: composables/api 의 wrapper 만 사용>
|
||||||
|
- 인증 토큰 저장 위치: <예: httpOnly 쿠키>
|
||||||
|
- 에러 핸들링 규칙: <작성>
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
|
||||||
|
- <예: 직접 $fetch 사용 금지>
|
||||||
|
- <예: 전역 이벤트 버스 사용 금지>
|
||||||
|
- <예: any 타입 사용 금지>
|
||||||
|
|
||||||
|
## 테스트
|
||||||
|
|
||||||
|
- 테스트 러너: <Vitest / Jest 등>
|
||||||
|
- 테스트 파일 위치: <소스 옆 / __tests__ 폴더>
|
||||||
|
- 최소 커버리지: <숫자>
|
||||||
39
templates/project/overview.md
Normal file
39
templates/project/overview.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 프로젝트 개요
|
||||||
|
|
||||||
|
> 이 파일은 `fe-common-rules/templates/project/overview.md` 에서 복사된 양식입니다.
|
||||||
|
> 프로젝트 세팅 후 실제 내용으로 채워주세요.
|
||||||
|
|
||||||
|
## 서비스
|
||||||
|
|
||||||
|
- **이름**: <프로젝트 이름>
|
||||||
|
- **설명**: <한 줄 설명>
|
||||||
|
- **배포 환경**: <dev / staging / production URL 또는 환경>
|
||||||
|
- **저장소**: <git 주소>
|
||||||
|
|
||||||
|
## 기술 스택
|
||||||
|
|
||||||
|
- **Framework**: <예: Nuxt 4 / Next 15 / ...>
|
||||||
|
- **UI**: <예: Vue 3 <script setup> / React 19 / ...>
|
||||||
|
- **Language**: TypeScript (strict)
|
||||||
|
- **Styling**: <예: Tailwind CSS / styled-components / ...>
|
||||||
|
- **상태관리**: <예: Pinia / Zustand / Redux Toolkit / ...>
|
||||||
|
- **테스트**: <예: Vitest + Playwright / Jest + RTL / ...>
|
||||||
|
- **패키지매니저**: <pnpm / npm / yarn>
|
||||||
|
|
||||||
|
## 주요 기능
|
||||||
|
|
||||||
|
- <기능 1>
|
||||||
|
- <기능 2>
|
||||||
|
- <기능 3>
|
||||||
|
|
||||||
|
## 팀 / 오너
|
||||||
|
|
||||||
|
- 오너: <팀 또는 담당자>
|
||||||
|
- 문의 채널: <Slack 채널 / 이메일>
|
||||||
|
- 온콜/긴급 연락: <필요 시>
|
||||||
|
|
||||||
|
## 참고 문서
|
||||||
|
|
||||||
|
- 기획 문서: <링크>
|
||||||
|
- 디자인 시스템: <링크>
|
||||||
|
- API 스펙: <링크>
|
||||||
Reference in New Issue
Block a user