Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
## 로딩 UI 원칙

- 화면 전환과 데이터 조회 중에 `불러오는 중…`, `확인 중…` 같은 문장형 로딩 안내를 화면에 노출하지 않는다.
- 최초 로딩에는 완료 후 화면과 배치·크기가 비슷한 스켈레톤을 우선 사용한다. 작은 영역에는 공통 `LoadingIndicator` 같은 간결한 로딩 표시를 사용한다.
- 최초 로딩에는 완료 후 화면과 배치·크기가 비슷한 스켈레톤을 우선 사용한다. 작은 영역에는 공통 `Spinner`(`src/components/ui/spinner`) 같은 간결한 로딩 표시를 사용한다.
- 제목, 진행 현황, 버튼, 목록의 자리를 미리 확보해 로딩 전후에 화면이 튀거나 다른 화면이 잠깐 끼어 보이지 않게 한다.
- 백그라운드 재조회에서는 이미 표시된 유효한 데이터를 유지하고, 새 데이터가 준비되면 갱신한다. 재조회만으로 전체 화면을 비우거나 금액을 임시 값으로 바꾸지 않는다.
- 로딩 상태는 `role="status"`, `aria-label`, `aria-busy`, `sr-only` 등으로 접근성 도구에 전달할 수 있다. 화면에서 문구를 숨기는 것과 접근성 안내를 제거하는 것은 구분한다.
Expand All @@ -28,7 +28,7 @@
- 기본 흐름은 최초 조회 → 캐시 재사용 → 변경 이벤트에 따른 갱신이다. 탭 이동이나 컴포넌트 재마운트, 브라우저 포커스 복귀만으로 유효한 데이터를 무조건 재조회하지 않는다.
- 공용 데이터는 브로드캐스트를 받아 해당 범위만 갱신한다. 이벤트가 변경 사실이나 ID만 전달하면 관련 캐시를 무효화하고 필요한 데이터를 재조회한다. 브로드캐스트 후 HTTP 조회도 이벤트 기반 갱신에 포함한다.
- 비활성 탭에서 변경 알림을 받으면 캐시에 갱신 필요 상태를 남기고, 해당 탭 복귀 시 반영한다. 중복 이벤트와 연속 변경으로 같은 요청이 반복되지 않게 한다.
- 본인 수정은 서버 응답으로 캐시를 반영한다. 응답만으로 상태를 확정할 수 없거나 충돌·정합성 확인이 필요한 경우에만 관련 범위를 재조회한다.
- Tanstack Query기본 원칙에 따라 서버 상태를 Source Of Truth로 두고 제한적인 상황에서만 서버를 호출하지 않고 캐싱해서 사용한다.
- 준비물 같은 개인 데이터에 팀 공동 편집용 실시간 동기화를 일괄 적용하지 않는다. 정상 조회한 상태는 재사용하고, 본인 수정·명시적 새로고침·오류 복구 등 실제 갱신 사유가 있을 때 조회한다.
- 최초 진입 또는 캐시 부재, 연결 끊김 후 브로드캐스트 누락 복구, 오류 재시도, 명시적 새로고침은 재조회 사유가 될 수 있다. 예외를 추가할 때는 구체적인 갱신 사유와 범위를 코드에 명시한다.
- 일반 탭 이탈을 이유로 목록과 캐시를 삭제하지 않는다. 로그아웃·계정 변경·접근 권한 상실 시에는 관련 데이터를 즉시 제거하고, 여행 변경 시에는 해당 데이터의 범위와 수명 정책에 맞게 정리한다.
Expand Down
9 changes: 5 additions & 4 deletions Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
예를들어서 피그마 페이지에 폰트가 17px이고 시멘틱 토큰과 css변수가 없다면 임의값을 사용한다.

### 1-3. 컴포넌트
목표는 shadcn이다. 아직 설치 전이라 기존 공용 컴포넌트와 병행하고, 무엇을 쓸지는 [핵심 원칙 1-1](docs/code-conventions/principles.md)을 따른다.
목표는 shadcn(Base UI)이다. 옮기는 동안 기존 공용 컴포넌트와 병행하고, 무엇을 쓸지는 [핵심 원칙 1-1](docs/code-conventions/principles.md)을 따른다.
- 공용: `src/components/<도메인>/`(`layout`, `place`, `map`, `settings` …)
- shadcn: 설치 후 `src/components/ui/`
- shadcn: `src/components/ui/`
- 특정 페이지 전용: `src/app/**/_components/`

### 1-4. Figma 디자인 시스템
Expand All @@ -35,7 +35,8 @@
- 11 [Tabs](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5138-2632) — Tabs Line, Tabs Chip, Tab Item
- 12 [Toast](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5285-4979) — Toast
- 13 [Alert Dialog](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5619-553) — Alert Dialog
- 14·15 [Chip · Badge](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5624-159) — Chip(Solid·Outline), Badge(Weak·Solid·Outline, 톤 6종)
- 14 [Chips](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5624-159) — Chip(Solid·Outline)
- 15 [Badges](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5864-12983) — Badge(Weak·Solid·Outline, 톤 6종)
- [Update Log](https://www.figma.com/design/Va7ys9C4lx9PIHOGvlaAID/Design-System?node-id=5635-11) — 디자인 시스템 변경 기록

---
Expand Down Expand Up @@ -111,7 +112,7 @@ import { MapPinIcon } from "@/assets/icons";

### 3-3. 이벤트 피드백
- 사용자 액션에는 결과 피드백이 있어야 한다
- alert는 toast, confirm은 `ConfirmDialog`를 쓰고 요구조건에 맞춘다(shadcn 설치 후 기준은 [핵심 원칙 1-1](docs/code-conventions/principles.md))
- alert는 toast, confirm은 `ConfirmDialog`(ui `AlertDialog` 위)를 쓰고 요구조건에 맞춘다(shadcn 부품으로 옮기는 기준은 [핵심 원칙 1-1](docs/code-conventions/principles.md))
- 비동기 동작 중 중복 실행 방지(핵심 원칙 2-4)


Expand Down
21 changes: 21 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
9 changes: 9 additions & 0 deletions docs/analytics/ga4-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@

운영자가 만드는 UTM 값에는 이름, 이메일, 전화번호, 검색어 등 개인을 식별하거나 사용자가 입력한 값을 넣지 않는다. 허용 키를 추가하려면 실제 분석 목적과 개인정보 처리방침의 수집 범위를 먼저 검토한다.

## 초대 링크 캠페인 보존

- 복사·기본 공유 시 외부 초대 링크에 `utm_source=member_invite`, `utm_medium=referral`, `utm_campaign=invite`를 붙인다. 플랫폼이나 초대한 개인을 구분하는 값이 아니다.
- 링크에 기존 `utm_*`가 하나라도 있으면 빈 값·불완전한 캠페인도 그대로 두고 기본 캠페인을 채우거나 섞지 않는다.
- 초대 진입 URL의 허용된 UTM만 초대 코드와 함께 같은 탭의 pending invite 문맥으로 보존한다. 로그인·약관 동의·오류 재시도와 인증 후 초대 복귀에 전달하고, 인증 완료의 복귀 주소를 만든 뒤 소비한다. 다른 초대가 들어오면 이전 문맥을 교체하며 인증된 초대 진입에서도 남은 문맥을 정리한다.
- 이 저장은 초대 인증 이동의 URL 문맥이며 이벤트 큐가 아니다. 동의 전 이벤트를 저장하거나 소급 전송하지 않는다. 일반 로그인·다른 내부 탐색에는 기본 캠페인을 만들지 않는다.
- OAuth state와 인증 토큰에 캠페인을 싣지 않는다. 원래 referrer 복원, 브라우저 간 연결, 초대 코드·초대자 ID의 분석 전송은 하지 않는다. 분석 URL 정규화와 User-ID 설정·해제는 기존 정책을 유지한다.
- 외부 브라우저가 같은 탭 저장소를 공유한다고 가정하지 않는다. 이 코드만으로 GA 세션 귀속이나 신규 사용자 집계가 소급 변경되는 것은 보장하지 않는다. 인증 도메인 referral 제외 등 운영 설정은 별도 관리한다.

## 배포 환경 변수

프로덕션에는 운영 데이터 스트림의 측정 ID만 설정한다.
Expand Down
6 changes: 3 additions & 3 deletions docs/code-conventions/anti-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
- ✓ prop을 더하기 전에 계산 → slot → `variant` → 화면 훅 순서로 본다. `<PlaceCard variant="compact" action={<AddToScheduleButton placeId={place.id} />} />`(원칙 2-2·1-4)

### 6. 있는 부품 다시 만들기
- 신호: 새 `fixed inset-0` 모달·바텀시트·로딩 표시, `SettingsActionButton`·`alertDialogButtonClass`가 맞는 자리에 복붙한 버튼 클래스, 새로 만든 범용 Button, `lucide-react`·새 아이콘 패키지
- ✓ `ConfirmDialog`·`SettingsDialog`·`MobileBottomSheet`·`LoadingIndicator`를 먼저 쓴다. 버튼은 `SettingsActionButton`·`alertDialogButtonClass`가 맞으면 쓰고, 아니면 그 자리 `<button>`에 토큰 클래스로 쓴다(범용 Button은 보류). 아이콘은 `@/assets/icons`(원칙 1-1, Design.md 2장).
- 신호: 새 `fixed inset-0` 모달·바텀시트·로딩 표시, 손으로 만든 메뉴·셀렉트·탭, `Button` 대신 복붙한 버튼 클래스, `useState`로 하나씩 든 대화상자 폼 값·오류, `lucide-react`·새 아이콘 패키지
- ✓ `src/components/ui/`의 부품(`Button`·`Dialog`·`AlertDialog`·`Drawer`·`DropdownMenu`·`Select`·`Combobox`·`Popover`·`Tabs`·`Spinner`·`Skeleton` 등)과 확인창 `ConfirmDialog`를 먼저 쓴다. 대화상자 폼은 TanStack Form(원칙 2-9). 아이콘은 `@/assets/icons`(원칙 1-1, Design.md 2장).

### 7. 미리 만든 추상화
- 신호: 사용처가 하나인 래퍼·옵션 객체·범용 컴포넌트, `useMount`·`useUpdateEffect` 같은 생명주기 훅, 쓰는 곳 없는 설정 레이어. 로직을 나누는 화면 훅은 사용처가 하나여도 걸리지 않는다.
Expand Down Expand Up @@ -87,7 +87,7 @@

### 17. 토큰 우회
- 신호: 토큰에 있는 값을 `text-[16px] leading-6`·`rounded-[8px]`·`#333`으로 쓴다. `bg-gray-800`·`shadow-md`(Tailwind 기본값), `dark-gray`(레거시 별칭), `text-body-s`(없는 토큰 이름), 타이포 토큰에 붙인 `font-semibold`, 템플릿 리터럴 className, 조립한 클래스 이름(`` `bg-${tone}` ``)
- ✓ `text-body-m-regular`, `rounded-md`, `text-text`, 클래스는 `cn("…", TONE_CLASS[tone], className)`(원칙 3-6).
- ✓ `text-body-m-regular`, `rounded-md`, `text-text`, 변형은 cva로 두고 `cn(tagVariants({ tone }), className)`(원칙 3-6).

### 18. div 버튼과 지운 포커스
- 신호: `<div onClick>`, 단순 이동을 `router.push` 버튼으로 만든다. 아이콘 버튼에 `aria-label`이 없다. placeholder만 있는 입력, `outline-none`으로 지운 포커스 표시
Expand Down
Loading
Loading