From 4e524eceee9f7f46248c727c1937ea79494f7d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKangShinGyu=E2=80=9D?= <“rkdtlseb@naver.com”> Date: Thu, 15 Oct 2026 17:42:31 +0900 Subject: [PATCH 1/4] =?UTF-8?q?chore(workflow):=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=20=EC=A2=85=EB=A5=98=20=EC=A0=95=EB=A6=AC=EC=99=80=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=8B=9C=EC=9E=91=C2=B7PR=20=EC=8A=A4?= =?UTF-8?q?=ED=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CONTRIBUTING에 브랜치 종류 표(fix·refactor·chore·docs 추가), PR 전 검증, 작업 공유 기준 정리 - PR 템플릿에 노션 링크 칸 추가 - Claude Code 스킬 start-work, open-pr 추가 - vitest에 테스트용 NEXT_PUBLIC 환경 변수를 넣어 36개 파일 로드 실패 수정 - .claude/settings.local.json을 gitignore에 추가 Co-Authored-By: Claude Opus 5.5 --- .claude/skills/open-pr/SKILL.md | 86 ++++++++++++++++++++++++++++++ .claude/skills/start-work/SKILL.md | 56 +++++++++++++++++++ .github/pull_request_template.md | 4 ++ .gitignore | 3 ++ CONTRIBUTING.md | 47 ++++++++++++++-- vitest.config.ts | 7 +++ 6 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 .claude/skills/open-pr/SKILL.md create mode 100644 .claude/skills/start-work/SKILL.md diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md new file mode 100644 index 00000000..445f4b1e --- /dev/null +++ b/.claude/skills/open-pr/SKILL.md @@ -0,0 +1,86 @@ +--- +name: open-pr +description: 작업 브랜치에 최신 대상 브랜치를 반영하고 lint·test·build를 통과시킨 뒤, PR 템플릿을 채워 확인을 받고 gh로 PR을 연다. "PR 올려줘", "PR 만들어줘", "작업 마무리하고 PR" 같은 요청이나 /open-pr에서 사용한다. 병합은 하지 않는다. +argument-hint: [노션 링크] +--- + +# PR 올리기 + +인자: $ARGUMENTS (노션 링크가 들어올 수 있다) + +규칙의 정본은 [CONTRIBUTING.md](../../../CONTRIBUTING.md)와 [PR 템플릿](../../../.github/pull_request_template.md)이다. + +병합은 리뷰어가 확인하고 직접 한다. 이 스킬은 PR을 여는 데까지만 하고 `gh pr merge`를 실행하지 않는다. + +## 0. 준비 + +- `gh auth status`가 실패하면 멈추고 `! gh auth login` 실행을 안내한다. +- 현재 브랜치가 `main`이나 `dev`면 멈춘다. +- 브랜치 접두사로 PR 대상과 제목 타입을 정한다. 표에 없는 접두사면 대상 브랜치를 묻는다. + + | 접두사 | PR 대상 | 제목 타입 | + | :-- | :-- | :-- | + | `feature/` | `dev` | `feat` | + | `fix/` | `dev` | `fix` | + | `refactor/` | `dev` | `refactor` | + | `chore/` | `dev` | `chore` | + | `docs/` | `dev` | `docs` | + | `hotfix/` | `main` | `fix` | + +- `gh pr view --json url,state`로 이 브랜치의 열린 PR이 있는지 확인한다. 이미 있으면 새로 만들지 않고, 1~3단계 후 push만 하고 기존 PR 주소를 알린다. + +## 1. 커밋하지 않은 변경 + +`git status --short`에 변경이 있으면 요약해 보여 주고 이번 PR에 넣을지 묻는다. 넣기로 한 변경만 AGENTS.md의 커밋 메시지 규칙으로 커밋한다. + +## 2. 최신 대상 브랜치 반영 + +```bash +git fetch origin --prune +git rev-list --count HEAD..origin/<대상> +``` + +0보다 크면 `git merge --no-edit origin/<대상>`으로 반영한다. rebase나 force-push는 하지 않는다. PR은 squash 병합되므로 작업 브랜치 안의 merge commit은 대상 브랜치에 남지 않는다. + +충돌이 나면 충돌 파일과 양쪽 변경 요지를 보여 주고 멈춘다. 어느 쪽을 살릴지 사용자와 정한 뒤 해결한다. 그만두려면 `git merge --abort`로 되돌릴 수 있다고 알린다. + +## 3. 검증 + +`git diff --name-only origin/<대상>...HEAD`가 문서(`*.md`)·`.claude/`·`.github/`뿐이면 검증을 생략하고 테스트 칸에 생략했다고 쓴다. + +그 외에는 순서대로 실행하고, 실패하면 다음 명령으로 넘어가지 않는다. + +1. `npm run lint` +2. `npm test` +3. `npm run build` + +실패하면 PR을 열지 않는다. 실패한 명령, 파일·테스트 이름, 핵심 에러 몇 줄만 보여 준다. 이번 변경 때문인지 판단해 알리고, 고칠지 묻는다. 이번 변경과 무관해 보이면 근거(변경 파일과 실패 위치의 관계)를 들어 그대로 PR을 열지 묻는다. 열기로 하면 테스트 칸에 실패 항목과 사유를 적는다. + +## 4. 본문 작성 + +- 제목: `<타입>(): <한국어 요약>`. scope는 커밋들의 대표 scope 하나이며, 없으면 생략한다. +- 본문은 PR 템플릿의 섹션 순서와 제목을 그대로 쓰고, HTML 주석은 지운다. + - `노션`: 인자나 대화에 링크가 있으면 넣는다. 없으면 5단계에서 묻는다. 링크가 없으면 섹션을 통째로 지운다. + - `변경 내용`: `git log origin/<대상>..HEAD`와 diff를 보고 리뷰어가 확인할 핵심만 3~5줄로 쓴다. 파일 목록을 나열하지 않는다. + - `변경 이유`: 1~2줄. 배경은 노션에 있으니 길게 쓰지 않는다. + - `테스트`: 3단계에서 통과한 명령만 `[x]`로 체크한다. 화면 확인 같은 수동 검증은 실제로 한 것만 적고, 하지 않은 항목은 지운다. + +## 5. 확인 + +PR을 열면 디스코드 알림이 자동으로 나가므로, 확인받기 전에는 push하거나 PR을 열지 않는다. + +대상 브랜치, 제목, 본문을 보여 주고 한 번에 묻는다. + +- 노션 링크(링크가 아직 없을 때만): "없음" 또는 직접 입력 +- 이대로 올릴지: 올리기 / 수정 + +## 6. PR 열기 + +본문은 저장소 밖 임시 파일(스크래치 디렉터리가 있으면 그곳, 없으면 `mktemp`)에 쓴다. + +```bash +git push -u origin <브랜치> +gh pr create --base <대상> --head <브랜치> --title "<제목>" --body-file <임시 파일> +``` + +PR 주소를 알린다. `hotfix/`였다면 "병합 후 main → dev 백머지 PR이 필요합니다"를 한 줄 덧붙인다. diff --git a/.claude/skills/start-work/SKILL.md b/.claude/skills/start-work/SKILL.md new file mode 100644 index 00000000..9ff23d1a --- /dev/null +++ b/.claude/skills/start-work/SKILL.md @@ -0,0 +1,56 @@ +--- +name: start-work +description: 새 작업 브랜치를 CONTRIBUTING.md 규칙대로 최신 기준 브랜치(dev, hotfix는 main)에서 만든다. "작업 시작", "브랜치 따줘", "새 작업 들어가자" 같은 요청이나 /start-work에서 사용한다. +argument-hint: <작업 설명> +--- + +# 작업 시작 + +작업 설명: $ARGUMENTS + +브랜치 종류·기준 브랜치의 정본은 [CONTRIBUTING.md](../../../CONTRIBUTING.md)의 `브랜치 종류` 표다. 이 문서와 다르면 CONTRIBUTING.md를 따른다. + +## 1. 브랜치 종류와 이름 + +- 작업 설명이 비어 있으면 무슨 작업인지 묻는다. +- 설명을 보고 `feature` / `fix` / `refactor` / `chore` / `docs` / `hotfix` 중 하나를 고른다. +- 버그 수정인데 "운영 장애", "바로 배포" 같은 단서가 없으면 `fix`와 `hotfix` 중 무엇인지 묻는다. `hotfix`는 기준 브랜치가 `main`이라 잘못 고르면 되돌리기 번거롭다. +- 이름은 `<종류>/<영어 소문자 kebab-case 2~4단어>`로 짓는다. 예: `feature/calendar-design` +- 같은 이름이 로컬(`git branch --list`)이나 origin(`git ls-remote --heads origin <브랜치>`)에 있으면 다른 이름을 고른다. squash 병합이 끝난 브랜치는 다시 쓰지 않는다. + +종류가 분명하면 묻지 않고 바로 진행한다. + +## 2. 현재 작업 상태 + +`git status --short`로 커밋하지 않은 변경을 확인한다. + +- 변경이 없으면 3단계로 간다. +- 변경이 있으면 멈추고 어떻게 할지 묻는다. stash·reset·checkout으로 임의로 치우지 않는다. + - 새 작업에 가져갈 변경: 그대로 새 브랜치를 만든다. `git switch -c`는 작업 트리 변경을 유지한다. + - 이전 작업의 변경: 사용자가 먼저 커밋하거나, 새 작업을 worktree(`.worktrees/`)에서 시작한다. + +## 3. 최신 기준 브랜치에서 만들기 + +기준 브랜치는 `hotfix`면 `main`, 나머지는 `dev`다. + +```bash +git fetch origin --prune +git switch --no-track -c <브랜치> origin/<기준> +``` + +worktree에서 시작하기로 했다면: + +```bash +git fetch origin --prune +git worktree add --no-track -b <브랜치> .worktrees/ origin/<기준> +``` + +worktree에는 `node_modules`와 `.env.local`이 없다. worktree에서 `npm ci`로 설치하고, `.env.local`은 원래 저장소에서 복사한다. `node_modules`를 심볼릭 링크로 연결하면 Turbopack 빌드가 "points out of the filesystem root"로 실패한다. + +`--no-track`은 새 브랜치가 `origin/dev`를 upstream으로 잡지 않게 한다. upstream은 처음 push할 때 `-u`로 자기 원격 브랜치에 연결한다. + +## 4. 보고 + +- 만든 브랜치 이름과 기준 커밋(`git log --oneline -1`)을 알린다. +- 화면이 바뀌는 작업이면 "노션에 넣을 작업 전 스크린샷을 지금 찍어 두세요"를 한 줄 덧붙인다. +- 작업 중에는 전체 테스트를 돌리지 않는다. lint·test·build는 PR 직전에 `/open-pr`에서 한 번 실행한다. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index af5b03dd..af8b4da0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,7 @@ +## 노션 + +- + ## 변경 내용 - diff --git a/.gitignore b/.gitignore index c89c0031..c95212c9 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ SERVICE_CONTEXT.md # Local git worktrees used for isolated feature development .worktrees/ +# Claude Code personal settings (project skills in .claude/skills are committed) +.claude/settings.local.json + # misc .DS_Store .idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2136336..5a9533ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,8 +14,22 @@ docs: 개발자 온보딩 보완 - `main`은 배포 기준 브랜치입니다. 직접 push하지 않고 모든 변경을 PR로 병합합니다. - `dev`는 기능을 통합하고 릴리스 전에 함께 검증하는 브랜치입니다. -- 일반 작업은 최신 `dev`에서 `feature/` 브랜치를 만들고 `dev`를 대상으로 PR을 엽니다. 브랜치는 하나의 작업 단위로 짧게 유지하며, squash 병합이 끝난 feature 브랜치는 다시 사용하지 않습니다. -- 긴급 수정은 최신 `main`에서 `hotfix/` 브랜치를 만들고 `main`을 대상으로 PR을 엽니다. +- 작업 브랜치는 아래 `브랜치 종류` 표의 기준 브랜치 최신 상태에서 만들고, 같은 표의 PR 대상으로 PR을 엽니다. +- 브랜치는 하나의 작업 단위로 짧게 유지하며, squash 병합이 끝난 브랜치는 다시 사용하지 않습니다. + +## 브랜치 종류 + +| 브랜치 | 용도 | 기준 브랜치 | PR 대상 | 커밋·PR 타입 | +| :-- | :-- | :-- | :-- | :-- | +| `feature/` | 새 기능, 화면·디자인 변경 | `dev` | `dev` | `feat` | +| `fix/` | 다음 배포에 함께 나가도 되는 버그 수정 | `dev` | `dev` | `fix` | +| `refactor/` | 동작이 바뀌지 않는 구조 개선 | `dev` | `dev` | `refactor` | +| `chore/` | 빌드·설정·의존성·개발 도구 | `dev` | `dev` | `chore` | +| `docs/` | 문서만 변경 | `dev` | `dev` | `docs` | +| `hotfix/` | 운영 장애처럼 바로 배포해야 하는 수정 | `main` | `main` | `fix` | + +- ``은 영어 소문자 kebab-case로 짧게 씁니다. 예: `feature/calendar-design`, `fix/logout-session` +- 표에 없는 접두사(`feat/`, `codex/` 등)로 새 브랜치를 만들지 않습니다. ## 공통 브랜치 흐름 @@ -23,7 +37,7 @@ docs: 개발자 온보딩 보완 | 병합 방향 | 방식 | | :-- | :-- | -| `feature/*` → `dev` | Squash and merge | +| `feature/*`, `fix/*`, `refactor/*`, `chore/*`, `docs/*` → `dev` | Squash and merge | | `dev` → `main` | Create a merge commit | | `hotfix/*` → `main` | Squash and merge | | `main` → `dev` 백머지 | Create a merge commit | @@ -39,3 +53,30 @@ docs: 개발자 온보딩 보완 - main으로 직접 merge 전 최소 1명의 리뷰가 필요하다. - PR 본문은 .github/pull_request_template.md 형식을 그대로 따른다. - PR 본문에 변경 이유와 테스트 방법을 간략히 적는다. +- PR을 열기 전에 대상 브랜치의 최신 상태를 작업 브랜치에 merge로 반영한다. 이미 push한 작업 브랜치는 rebase하거나 force-push하지 않는다. +- 병합은 리뷰어가 확인한 뒤 위 `공통 브랜치 흐름`의 방식으로 한다. + +## PR 전 검증 + +- 작업 중에는 전체 테스트를 돌리지 않는다. 필요한 확인만 한다. +- PR을 열기 전에 `npm run lint` → `npm test` → `npm run build`를 한 번 실행하고, 결과를 PR 본문 테스트 칸에 체크한다. +- 문서(`*.md`)·`.claude/`·`.github/`만 바뀐 PR은 생략할 수 있다. 생략하면 테스트 칸에 생략했다고 적는다. + +## 작업 공유 + +| 위치 | 내용 | +| :-- | :-- | +| 노션 작업 페이지 | 작업 배경(왜 시작했는지), 논의 내용, 전후 스크린샷, 개발 메모 | +| PR | 변경 내용, 짧은 변경 이유, 테스트 결과, 노션 페이지 링크 | +| 디스코드 | PR을 열면 GitHub Actions가 자동으로 알린다. 작업 내역을 따로 보내지 않는다. | + +- 노션 페이지가 없는 작은 작업은 PR 본문의 `노션` 칸을 지운다. + +## Claude Code 스킬 + +저장소의 `.claude/skills/`에 위 규칙을 따르는 스킬이 있다. 사용하려면 [GitHub CLI](https://cli.github.com/)를 설치하고 `gh auth login`으로 로그인한다. + +| 명령 | 하는 일 | +| :-- | :-- | +| `/start-work <작업 설명>` | 브랜치 종류·이름을 정하고 최신 기준 브랜치에서 브랜치를 만든다. | +| `/open-pr [노션 링크]` | 대상 브랜치 반영 → lint·test·build → 템플릿 작성 → 확인 후 PR을 연다. 병합은 하지 않는다. | diff --git a/vitest.config.ts b/vitest.config.ts index e6bfb347..a394921b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -19,5 +19,12 @@ export default defineConfig({ test: { environment: "node", include: ["src/**/*.test.{ts,tsx}"], + // clientEnv는 import 시점에 필수 값을 검사한다 — vitest는 .env.local을 process.env로 읽지 않으므로 테스트용 값을 넣는다 + env: { + NEXT_PUBLIC_API_BASE_URL: "http://localhost:8080", + NEXT_PUBLIC_GOOGLE_CLIENT_ID: "test-google-client-id", + NEXT_PUBLIC_GOOGLE_REDIRECT_URI: "http://localhost:3000/auth/callback", + NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: "test-google-maps-api-key", + }, }, }); From acc588cfc49f5392cc9df63aa01a93d553d88451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKangShinGyu=E2=80=9D?= <“rkdtlseb@naver.com”> Date: Thu, 15 Oct 2026 18:48:29 +0900 Subject: [PATCH 2/4] =?UTF-8?q?chore(workflow):=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8?= =?UTF-8?q?=EC=99=80=20check-screens=20=EC=8A=A4=ED=82=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - npm run screens: Playwright로 PC 크롬·사파리(WebKit)·안드로이드(Galaxy S24 에뮬레이션), simctl로 아이폰 시뮬레이터 스크린샷 - 전용 크롬 프로필에서 로그인해 localhost 인증 쿠키만 저장 - 3000번 dev 서버가 현재 폴더 코드인지 검사 - open-pr 검증에 화면 확인 단계, PR 템플릿에 화면 확인 항목 추가 - CONTRIBUTING에 화면 확인 기준 표 추가 Co-Authored-By: Claude Opus 5.5 --- .claude/skills/check-screens/SKILL.md | 69 +++++++ .claude/skills/open-pr/SKILL.md | 5 +- .github/pull_request_template.md | 1 + .gitignore | 3 + CONTRIBUTING.md | 19 +- package-lock.json | 30 +++ package.json | 4 +- scripts/check-screens.mjs | 253 ++++++++++++++++++++++++++ 8 files changed, 380 insertions(+), 4 deletions(-) create mode 100644 .claude/skills/check-screens/SKILL.md create mode 100644 scripts/check-screens.mjs diff --git a/.claude/skills/check-screens/SKILL.md b/.claude/skills/check-screens/SKILL.md new file mode 100644 index 00000000..561c09cf --- /dev/null +++ b/.claude/skills/check-screens/SKILL.md @@ -0,0 +1,69 @@ +--- +name: check-screens +description: 바뀐 화면을 PC 크롬·PC 사파리(WebKit)·안드로이드(크롬 기기 에뮬레이션)·아이폰 시뮬레이터 Safari에서 스크린샷으로 찍어 깨짐을 확인한다. "화면 확인", "스크린샷 찍어줘", "기기별로 봐줘" 같은 요청이나 /check-screens, /open-pr 검증 단계에서 사용한다. +argument-hint: [경로...] [before|after] +--- + +# 화면 확인 + +인자: $ARGUMENTS (경로와 라벨이 들어올 수 있다) + +어떤 변경에 어떤 환경을 볼지는 [CONTRIBUTING.md](../../../CONTRIBUTING.md)의 `화면 확인 기준` 표가 정본이다. 스크린샷은 `scripts/check-screens.mjs`(`npm run screens`)가 찍는다. + +## 0. 처음 한 번 준비 + +스크립트가 로그인 화면으로 이동됐다고 하거나 브라우저가 없다고 하면 아래를 안내하고 멈춘다. + +- PC 크롬·사파리·안드로이드 로그인: `npm run screens -- login`을 실행하면 크롬 창이 열린다. 구글 로그인을 끝내면 `~/.cache/uttae/check-screens/`에 저장되고 세 환경이 함께 쓴다. +- 아이폰 로그인: 시뮬레이터 Safari에서 `http://localhost:3000/login`을 열어 한 번 로그인한다. 시뮬레이터는 쿠키를 따로 쓴다. +- 브라우저: `npx playwright install chromium webkit` +- 시뮬레이터: Xcode가 있어야 한다. 기본 기기는 `iPhone 17`이고 `--iphone "<기기 이름>"`으로 바꾼다. + +## 1. 환경 고르기 + +CONTRIBUTING.md 표를 기준으로 `--targets`를 정한다. 값은 `pc-chrome`, `pc-safari`, `android`, `iphone`이다. + +- PC 화면을 바꾸면서 아래 중 하나를 건드렸으면 `pc-safari`를 넣는다: `fixed`·`sticky`, `h-screen`·`dvh`·`svh`, `safe-area`, `backdrop-blur`·`backdrop-filter`, 날짜·시간 input, 스크롤 동작(`overflow`, `scroll-snap`), 16px 미만 글자의 input +- 판단이 어려우면 4개를 모두 찍는다. + +## 2. 경로 고르기 + +- 인자로 경로를 받았으면 그대로 쓴다. +- 없으면 `git diff --name-only origin/dev...HEAD`와 작업 트리 변경에서 화면을 찾는다. + - `src/app/**/page.tsx`는 경로로 바꾼다. `(main)` 같은 괄호 그룹은 빼고, `[roomId]` 같은 동적 값은 실제 값이 필요하다. + - 컴포넌트는 사용처를 검색해 그 컴포넌트가 보이는 페이지를 찾는다. +- 동적 값(방 ID 등)이나 화면에 들어가는 방법을 모르면 사용자에게 예시 URL을 묻는다. 대화 안에서 받은 값은 다시 묻지 않는다. +- 한 번에 1~4개 경로만 찍는다. + +## 3. dev 서버 + +스크립트는 3000번 dev 서버가 지금 폴더의 코드인지 검사한다. 백엔드 CORS와 구글 로그인이 3000번만 허용하므로 다른 포트는 쓰지 않는다. + +- 서버가 없으면 이 폴더에서 `npm run dev`를 백그라운드로 띄우고, 응답할 때까지 기다린다. +- 다른 폴더(다른 worktree)의 서버가 3000번을 쓰고 있으면 멈추고 사용자에게 알린다. 그 서버를 임의로 끄지 않는다. + +## 4. 찍기 + +```bash +npm run screens -- --path <경로> [--path <경로>] --targets <대상,...> --label <라벨> +``` + +- 라벨: 작업 시작 전에는 `before`, PR 전에는 `after`. 노션 전후 비교에 그대로 쓴다. 생략하면 시간이 붙는다. +- 긴 화면은 `--full`, 안드로이드 기기를 바꾸려면 `--android-device "Galaxy Z Fold 7"`처럼 Playwright 기기 이름을 넘긴다. +- 지도처럼 늦게 그려지는 화면은 `--wait 4000`, 아이폰은 `--iphone-wait 6000`으로 늘린다. + +## 5. 확인 + +찍은 이미지를 직접 열어 본다. 출력 표와 `.screens/<라벨>/results.json`도 확인한다. + +- 로그인 화면이 찍혔으면(아이폰은 이미지로 판단) 0단계 로그인을 안내하고 멈춘다. +- 콘솔 에러가 있으면 `results.json`의 메시지를 요약한다. +- 넘침, 잘림, 겹침, 가로 스크롤, 빈 화면, 끝나지 않는 스켈레톤을 찾는다. +- Figma 링크가 있는 디자인 작업이면 Figma 노드와 px 단위로 비교한다. PC 스크린샷은 1배율이라 이미지 px이 CSS px과 같다. 안드로이드·아이폰은 3배율이다. +- 터치, 드래그, 키보드, 포커스는 스크린샷으로 확인할 수 없다. 해당 변경이면 사람이 직접 눌러 볼 항목을 구체적으로 적는다. + +## 6. 보고 + +- 환경별 결과를 한 줄씩 적는다(이상 없음 / 문제 요약). +- 이미지 폴더 경로(`.screens/<라벨>/`)를 알린다. +- 사람이 직접 확인할 조작 항목이 있으면 목록으로 남긴다. diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md index 445f4b1e..99297fd3 100644 --- a/.claude/skills/open-pr/SKILL.md +++ b/.claude/skills/open-pr/SKILL.md @@ -53,8 +53,9 @@ git rev-list --count HEAD..origin/<대상> 1. `npm run lint` 2. `npm test` 3. `npm run build` +4. 화면 확인: 화면에 영향을 주는 파일(`src/app/`, `src/components/`, `src/styles/`, `src/assets/`, `public/`, `*.css`)이 바뀌었으면 [check-screens](../check-screens/SKILL.md) 순서대로 `after` 라벨로 찍고 확인한다. CONTRIBUTING.md `화면 확인 기준` 표에서 고른 환경만 찍는다. -실패하면 PR을 열지 않는다. 실패한 명령, 파일·테스트 이름, 핵심 에러 몇 줄만 보여 준다. 이번 변경 때문인지 판단해 알리고, 고칠지 묻는다. 이번 변경과 무관해 보이면 근거(변경 파일과 실패 위치의 관계)를 들어 그대로 PR을 열지 묻는다. 열기로 하면 테스트 칸에 실패 항목과 사유를 적는다. +실패하거나 화면이 깨졌으면 PR을 열지 않는다. 실패한 명령, 파일·테스트 이름, 핵심 에러 몇 줄만 보여 준다. 이번 변경 때문인지 판단해 알리고, 고칠지 묻는다. 이번 변경과 무관해 보이면 근거(변경 파일과 실패 위치의 관계)를 들어 그대로 PR을 열지 묻는다. 열기로 하면 테스트 칸에 실패 항목과 사유를 적는다. ## 4. 본문 작성 @@ -63,7 +64,7 @@ git rev-list --count HEAD..origin/<대상> - `노션`: 인자나 대화에 링크가 있으면 넣는다. 없으면 5단계에서 묻는다. 링크가 없으면 섹션을 통째로 지운다. - `변경 내용`: `git log origin/<대상>..HEAD`와 diff를 보고 리뷰어가 확인할 핵심만 3~5줄로 쓴다. 파일 목록을 나열하지 않는다. - `변경 이유`: 1~2줄. 배경은 노션에 있으니 길게 쓰지 않는다. - - `테스트`: 3단계에서 통과한 명령만 `[x]`로 체크한다. 화면 확인 같은 수동 검증은 실제로 한 것만 적고, 하지 않은 항목은 지운다. + - `테스트`: 3단계에서 통과한 명령만 `[x]`로 체크한다. 화면 확인은 찍은 환경만 적고(예: `pc-chrome, android, iphone`), 찍지 않았으면 항목을 지운다. 사람이 직접 한 수동 검증은 사용자에게 들은 것만 적는다. ## 5. 확인 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index af8b4da0..a5911b45 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,4 +12,5 @@ - [ ] `npm run lint` - [ ] `npm test` - [ ] `npm run build` +- [ ] 화면 확인(`npm run screens`): - [ ] 그 외 수동 검증: diff --git a/.gitignore b/.gitignore index c95212c9..10fe13a1 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ SERVICE_CONTEXT.md # Claude Code personal settings (project skills in .claude/skills are committed) .claude/settings.local.json +# check-screens 스크린샷 결과 (npm run screens) +.screens/ + # misc .DS_Store .idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a9533ab..3669264b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,6 +61,22 @@ docs: 개발자 온보딩 보완 - 작업 중에는 전체 테스트를 돌리지 않는다. 필요한 확인만 한다. - PR을 열기 전에 `npm run lint` → `npm test` → `npm run build`를 한 번 실행하고, 결과를 PR 본문 테스트 칸에 체크한다. - 문서(`*.md`)·`.claude/`·`.github/`만 바뀐 PR은 생략할 수 있다. 생략하면 테스트 칸에 생략했다고 적는다. +- 화면이 바뀌면 아래 기준으로 환경을 골라 스크린샷을 확인한다. + +### 화면 확인 기준 + +| 변경 종류 | PC 크롬 | PC 사파리 | 안드로이드 (크롬 에뮬레이션) | 아이폰 (시뮬레이터) | +| :-- | :-: | :-: | :-: | :-: | +| 로직·데이터만 (화면 변화 없음) | ✓ | | | | +| PC 레이아웃·스타일 | ✓ | 사파리 주의 CSS일 때 | | | +| 모바일 레이아웃 | | | ✓ | ✓ | +| 터치·드래그·키보드·포커스·바텀시트 | | | ✓ + 직접 조작 | ✓ + 직접 조작 | +| `dev` → `main` 배포 | ✓ | ✓ | ✓ | ✓ | + +- 사파리 주의 CSS: `fixed`·`sticky`, `h-screen`·`dvh`·`svh`, `safe-area`, `backdrop-filter`, 날짜·시간 input, 스크롤 동작, 16px 미만 글자의 input +- 앱은 User-Agent로 모바일 화면을 판단한다. 크롬 창 크기만 줄이면 모바일 화면이 나오지 않으므로 기기 에뮬레이션을 쓴다. +- `npm run screens -- --path /home --targets pc-chrome,android --label after`로 찍으면 `.screens/after/`에 저장된다. 처음에는 `npm run screens -- login`으로 로그인 상태를 저장하고, 아이폰 시뮬레이터 Safari에서도 한 번 로그인한다. +- 백엔드 CORS와 구글 로그인이 `localhost:3000`만 허용하므로, 여러 worktree를 쓸 때도 확인할 폴더의 dev 서버 하나만 3000번으로 띄운다. ## 작업 공유 @@ -79,4 +95,5 @@ docs: 개발자 온보딩 보완 | 명령 | 하는 일 | | :-- | :-- | | `/start-work <작업 설명>` | 브랜치 종류·이름을 정하고 최신 기준 브랜치에서 브랜치를 만든다. | -| `/open-pr [노션 링크]` | 대상 브랜치 반영 → lint·test·build → 템플릿 작성 → 확인 후 PR을 연다. 병합은 하지 않는다. | +| `/check-screens [경로...] [before\|after]` | 위 `화면 확인 기준`대로 환경을 골라 스크린샷을 찍고 깨짐을 확인한다. | +| `/open-pr [노션 링크]` | 대상 브랜치 반영 → lint·test·build·화면 확인 → 템플릿 작성 → 확인 후 PR을 연다. 병합은 하지 않는다. | diff --git a/package-lock.json b/package-lock.json index 675fe989..8e710173 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "eslint": "^9", "eslint-config-next": "16.2.1", "jsdom": "^30.0.1", + "playwright": "^1.63.0", "react-test-renderer": "^19.2.4", "sharp": "^0.35.3", "tailwindcss": "^4", @@ -10977,6 +10978,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", diff --git a/package.json b/package.json index 9432c968..85be1475 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "lint": "eslint", - "test": "vitest run" + "test": "vitest run", + "screens": "node scripts/check-screens.mjs" }, "dependencies": { "@amplitude/unified": "^1.1.28", @@ -44,6 +45,7 @@ "eslint": "^9", "eslint-config-next": "16.2.1", "jsdom": "^30.0.1", + "playwright": "^1.63.0", "react-test-renderer": "^19.2.4", "sharp": "^0.35.3", "tailwindcss": "^4", diff --git a/scripts/check-screens.mjs b/scripts/check-screens.mjs new file mode 100644 index 00000000..f1e8a70c --- /dev/null +++ b/scripts/check-screens.mjs @@ -0,0 +1,253 @@ +import { execFileSync, spawn, spawnSync } from "node:child_process"; +import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { homedir } from "node:os"; +import path from "node:path"; +import { parseArgs } from "node:util"; + +import { chromium, devices, webkit } from "playwright"; + +// 저장소 루트에서 실행한다. +// npm run screens -- login # 최초 1회, 크롬 창에서 구글 로그인 +// npm run screens -- --path /home --path /plan/123 --label after +// npm run screens -- --path /home --targets android,iphone +// 결과: .screens/