- 공식 용어는
docs/reference/03_공식_용어사전.md를 기준으로 사용한다. - 코드 식별자·주석·UI 텍스트·커밋 메시지에 공식 영문 용어를 그대로 쓴다.
- User, Place, Record, Context, Keyword, Collection, CollectionRecord, Shelf, Library, Follow, Feed, Feed Event, Impression
- 폐기·비공식 용어를 쓰지 않는다.
Memory→Record또는Context(문맥에 맞게)Folder→Collection,Tag(사용자 입력 태그) →Keyword(AI 매핑)Bookmark/Post/Review같은 임의 명칭 금지
- Shelf·Library는 화면·조회 개념이며 물리 테이블이 아니다(
docs/reference/06_데이터모델_및_무결성.md1.4). 저장 구조를 가정하지 않는다.
-
any금지. 불가피하면unknown후 좁히기. 타입을 못 정하면 스펙(docs/api-contract.md)을 먼저 확인한다. -
컴포넌트에서 API를 직접 호출하지 않는다. 데이터 흐름은
docs/architecture.md를 따른다(화면 → Hook → API 함수 → HTTP Client). -
서버 상태(TanStack Query)와 UI 상태(Context API)를 한 곳에 섞지 않는다.
-
검증 스키마는 Zod로 정의하고 API 경계에서 파싱한다.
-
하나의 사실을 두 변수가 나눠 들지 않는다. 특히 외부 라이브러리 인스턴스의 준비 여부가 렌더에 영향을 준다면 ref가 아니라 state로 든다 — ref는 갱신해도 리렌더가 없어 effect가 다시 돌지 않는다(
docs/troubleshooting/2026-08-06-map-remount-ref-state-race.md, Jira 작업). -
Tailwind 클래스는 리터럴로만 쓴다. Tailwind는 소스를 원시 텍스트로 스캔해 양방향으로 조용히 틀린다 — 문자열 조작으로 만든 클래스는 스캔되지 않아 스타일이 없고, 주석에 클래스처럼 생긴 문자열을 적으면 스캔돼 없는 규칙이 생긴다. 조건부는 완성된 리터럴 중 하나를 고르는 형태로 쓰고, 주석에서 클래스를 언급할 때 대괄호 arbitrary value를 그대로 적지 않는다(
docs/troubleshooting/2026-08-06-collection-spread-redesign-lessons.md·2026-08-06-silent-css-traps-nav-shell.md). -
새 컴포넌트를 만들기 전에 기존 구현을 먼저 찾는다. 병렬 작업에서 배정받은 "내 파일 목록"은 쓰기 권한의 경계이지 탐색 범위의 경계가 아니다. 배정 밖 파일을 고쳐야 하면 우회해서 새로 만들지 말고 보고한다(Jira 작업).
-
모듈 스코프 플래그·전역 상태의 소비자를 지울 때 생산자도 함께 확인한다. 타입으로 이어지지 않아 컴파일러가 알려주지 않는다 — 읽는 쪽을 지우면 쓰는 쪽이 아무도 안 보는 값을 계속 쓴다. 당장 지울 수 없으면 후속 정리 대상으로 명시한다(Jira 작업 → 350).
-
httpClient로FormData(multipart) 요청을 보낼 때는Content-Type헤더를 지운다.httpClient가 기본 헤더로Content-Type: application/json을 고정하고 있어서, 그대로 두면 브라우저가 multipart boundary를 붙이지 못해 요청이 깨진다(서버가image/jpeg등을 JSON으로 파싱하려다 실패).const formData = new FormData(); formData.append('image', image, image.name); const { data } = await httpClient.post('/places/suggestions', formData, { headers: { 'Content-Type': undefined }, });
'multipart/form-data'를 직접 지정하지 않는다 — boundary 값을 브라우저가 요청 생성 시점에 채워야 하므로, 헤더를 아예 비워(undefined) 브라우저가 자동으로 채우게 한다. 예시:suggestPlacesFromImage.ts(Jira 작업).
- 브랜치:
main(배포),dev(통합). 기능은 항상dev에서 분기한다. - Jira 이슈키 형식:
Jira-숫자(예:Jira 작업).
| 대상 | 형식 | 예시 |
|---|---|---|
| 브랜치 | feature/<jira-key>-<설명> |
feature/<jira-key>-record-map |
| 커밋 | type(Jira-<번호>): 설명 |
feat(Jira 작업): 지도 마커 조회 |
| PR 제목 | [Jira-<번호>] 설명 |
[Jira 작업] 지도 마커 조회 |
- 커밋
type:feat,fix,refactor,docs,chore,test,style등 Conventional Commits. - 설명은 한국어로 간결하게 쓴다.
| 방향 | 방식 | 이유 |
|---|---|---|
feature/* → dev |
Squash and merge | 기능 단위로 커밋 히스토리를 한 줄로 압축 |
dev → main |
Merge commit | 릴리스 시점을 병합 커밋으로 보존 |
- hotfix는
main에 반영한 뒤 반드시dev에도 반영한다(PR 템플릿 하단 항목 참조).
아래 순서를 따른다. 각 단계는 사용자의 명시적 신호로만 다음 단계로 넘어간다.
| 단계 | 주체 | 내용 |
|---|---|---|
| 1. 작업 제시 | 사용자 | 대화로 오늘 할 일을 말한다 |
| 2. 티켓 작성 | 에이전트 | /ticket으로 인터뷰해 티켓 본문을 만든다. git 조작 없음 |
| 3. 티켓 등록 | 사용자 | Jira에 등록하고 이슈키를 알려준다 |
| 4. 브랜치 생성·계획 보고 | 에이전트 | 이슈키를 받으면 dev에서 브랜치를 만들고(3장 명명 규칙) 작업 계획을 보고한다 |
| 5. 착수 승인 | 사용자 | "진행해" |
| 6. 작업·커밋 전 보고 | 에이전트 | 코드를 고치고 커밋하지 않은 채 변경 내용을 보고하고 멈춘다 |
7. /pr |
사용자 | |
| 8. 커밋·push·PR·머지 | 에이전트 | 커밋을 기능별로 나누고 push, PR 생성. CI 통과·충돌 없음을 확인한 뒤 squash 머지까지 진행한다 |
- 이슈키 없이 브랜치를 만들지 않는다. 다른 작업의 이슈키를 재사용하는 것도 금지다.
- 6단계를 건너뛰지 않는다. 검사(타입체크·테스트)가 다 통과해도 커밋 전에 보고하고 멈춘다.
- 머지는
/pr승인 이후 에이전트가 진행한다. 머지 전 CI 통과와 충돌 없음(mergeable)을 확인하고, 실패·충돌 시에는 머지하지 않고 보고한다. - 위 절차 밖에서는
checkout·branch·pull·push·merge·commit·add를 실행하지 않는다. 필요하면 사용자에게 요청한다. - 판단 근거는 추측이 아니라 실제 문서·코드·레포 상태여야 한다. 다른 레포(
Team-PinLog/back·docs) 상태가 걸리면gh로 직접 확인한다.
- 목록 순서는 서버 응답 순서를 그대로 사용한다. 프론트에서 재정렬하지 않는다.
- Feed
position·requestId는 응답 값을 그대로 사용하고 프론트에서 재계산하지 않는다. - Keyword의 식별 키는
code다. 표시 문자열(display_name, 구 컬럼명label)을 상태 키·매핑 키로 쓰지 않는다. - [해결됨] origin(Team-PinLog/docs)이
keyword_preset의 DB 컬럼명을 label→display_name으로 변경함(2026-07 업데이트). API 응답(keywords)은 원래도 code/label 구분 없는 순수 문자열 배열이라 JSON 계약 변경 없음(08_API_명세§6.1,05_AI_설계§12.1, 2026-08 재확인).
/dev/*등 개발·QA 전용 라우트는router.tsx에서import.meta.env.DEV조건으로 라우트 트리 등록 자체를 감싼다(예시:placeRecordPreviewRoute). Vite가 빌드 타임에 상수로 치환해 프로덕션 빌드에서는 조건이false로 굳어지므로 라우트가 등록되지 않아 해당 경로는NotFoundPage로 떨어진다 — URL을 알아도 접근할 수 없다. (페이지 컴포넌트 자체는 모듈 그래프에서 정적으로 참조되므로 번들에는 남지만, 라우터가 마운트하지 않아 실행되지는 않는다.)beforeLoad인증 가드(requireLoggedIn등)로는 대체하지 않는다. 개발 전용 라우트는 종종 "로그인 없이 테스트 세션을 발급"하는 등 인증 가드와 목적이 상충하는 기능을 담기 때문이다.- 개발 전용 라우트가 호출하는 API가 있다면, 그 API가 운영 백엔드에도 노출되어 있는지 별도로 확인한다. 프론트에서 라우트를 숨겨도 백엔드 엔드포인트 자체가 운영에 살아 있으면 URL을 아는 누구나 호출할 수 있다 — 이건 프론트만으로는 막을 수 없는 백엔드 이슈다(Jira 작업).