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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"autoPort": true,
"env": {
"AXIS_PRICE_SOURCE": "seed"
}
},
"_comment": "seed는 로컬 데모 전용. 프로덕션/Vercel에는 넣지 말 것."

}
]
}
8 changes: 6 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ GOOGLE_SEARCH_CX=
# 로컬 검토 시에만 1로 켜고, 프로덕션에서는 보호된 환경변수로만 설정하세요.
AXIS_ADMIN=

# 가격 provider. seed = 데모/개발용 fixture, 실데이터 provider 연결 전에는 비워두면 가격 UI가 숨겨집니다.
AXIS_PRICE_SOURCE=seed
# 가격 provider.
# - 로컬 데모: seed
# - 프로덕션: naver (또는 coupang). seed 금지 — 가짜 가격이 노출됩니다.
# - 미설정: 가격 UI 숨김
AXIS_PRICE_SOURCE=


# 검색엔진 사이트 인증 (Google Search Console / Naver Search Advisor 등록 후 발급)
# NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: npm
- run: npm ci
- run: npm run lint
- run: npx tsc --noEmit
- run: npm test
- run: npm run build
env:
# Build must not require real secrets; seed only for compile-time pages.
AXIS_PRICE_SOURCE: seed
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ Axis 코드베이스에서 작업할 때 따라야 할 규칙. 프로젝트 개
- 타입 체크: `npx tsc --noEmit`.
- 데이터셋 변경 시 `dataset.test.ts` 무결성 검사 통과 확인.
- 스키마·결과 포맷을 바꾸면 `lib/comparison-cache.ts`의 `CACHE_VERSION`을 올려
구버전 캐시를 무효화한다 (현재 v8).
구버전 캐시를 무효화한다 (현재 **v9**).

## 데이터셋 작업

- 수동 검증 데이터: `lib/specs/dataset/{smartphones,earphones,laptops,tablets}.ts`
- 수동 검증 데이터: `lib/specs/dataset/{index,smartphones,earphones,laptops,tablets}.ts` (+ `kr/`)
- 제품명은 로케일 정규화됨 — `canonicalName`(한국어) + `nameEn` + `nameJa`.
EN/JA 로케일에서 어필리에이트 검색어·표시명이 이 필드로 결정된다.
- 새 제품 추가 시: `id`는 lowercase-kebab, spec 키는 카테고리 스키마에 존재해야 함.
Expand Down
68 changes: 68 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Axis Design System

## 1. Atmosphere & Identity

Axis feels like a quiet decision desk for high-stakes electronics. Surfaces stay calm and readable; accent color appears only on winners, actions, and focus. The signature is comparison clarity: short verdicts, clear winners, and numeric gaps shown as selectable line graphs rather than noise.

## 2. Color

### Palette

| Role | Token | Light | Dark | Usage |
|------|-------|-------|------|-------|
| Surface/primary | --bg | #ffffff | #0d0f14 | Page background |
| Surface/secondary | --bg-subtle | #f8f9fc | #111318 | Soft panels |
| Text/primary | --text | #0f172a | #e2e8f4 | Headlines, body |
| Text/muted | --muted | #64748b | #8891a6 | Notes, secondary |
| Accent/primary | --accent | #3454e8 | #5b78f5 | CTAs, winners, focus |
| Accent/secondary | --accent-2 | #7c5cff | #9474ff | Winner gradients |

### Rules
- Accent is for interactive or winning states only.
- Coverage notes and helper text use muted, never decorative color.
- Spec line graphs reuse accent/accent-2 for the path and best points.

## 3. Typography

- Body and cards: system UI stack already set in globals.
- Section titles in detail cards stay compact and high-contrast.
- Coverage notes: ~0.82–0.88rem, weight 500–600, muted.

## 4. Spacing

- Detail cards: existing padding (~1.5rem).
- Spec graph cards: 0.7–1.05rem vertical rhythm.
- Line chart viewBox ~640×220 with soft gridlines.

## 5. Components

### CoverageNote
- Short muted sentence near hero input and results graph section.
- Copy pattern: `{year}년 이후 출시 제품부터 비교 가능합니다` (localized).

### SpecBarGraphs
- One compare bar chart at a time for numeric comparable fields.
- Products are shown side-by-side as vertical bars.
- Bar height follows raw numeric magnitude (larger number = taller bar). No better/worse inversion.
- Chip selector shows exactly one spec chart to keep the page short.
- Product labels + numeric values stay visible; no recommendation highlight in the graph.
- Keep existing cmp-table below as the exact-value source.

### Spec table
- Existing `.cmp-table` remains source of exact values.

## 6. Motion

- Verdict bar width transition remains ~0.7s; bar height transition matches verdict bars (~0.7s).

## 7. Accessibility

- Graphs must remain readable without color alone: product labels + numeric values stay visible.
- Spec chips use `role="radio"` with `aria-checked` for single-select.
- Focus rings use accent outline.
- Do not rely on percentage labels for the verdict bars; numeric values stay on graph rows.

## 8. Accepted Debt

- No DESIGN.md existed; this file extracts the current Axis look rather than redesigning.
- React-grab / react-scan / react-doctor deferred pending PM package approval.
36 changes: 18 additions & 18 deletions DEV_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Axis — 개발 노트

> 마지막 업데이트: 2026-06-11
> 테스트: `npm test` 통과 기준 유지 · 캐시 버전: **v8**
> 마지막 업데이트: 2026-07-21
> 테스트: `npm test` 통과 기준 유지 · 캐시 버전: **v9**
> 프로덕션: https://axis-app-beta.vercel.app
>
> 이 문서는 개발 단일 참조점이다. 제품 방향·진행 현황·아키텍처·남은 작업을 모두 담는다.
Expand Down Expand Up @@ -46,7 +46,7 @@
```
사용자 쿼리 ("에어팟 프로 vs 버즈")
[1] 캐시 확인 (Supabase comparison_cache, v8|query|locale|country)
[1] 캐시 확인 (Supabase comparison_cache, v9|query|locale|country)
↓ 미스
[2] expandComparisonOptions() — 브랜드명 → 최신 모델 확장
[3] detectCategory() — 카테고리 분류
Expand Down Expand Up @@ -88,9 +88,9 @@
|---------|------|------|
| 스마트폰 | `smartphones.ts` | 55 |
| 이어폰 | `earphones.ts` | 18 |
| 노트북 | `laptops.ts` | 26 |
| 노트북 | `laptops.ts` | 28 |
| 태블릿 | `tablets.ts` | 23 |
| **합계** | | **122** |
| **합계** | | **124** |

추가로 다나와 자동 수집 데이터(`dataset/kr/`)가 수동 데이터 뒤에 병합됨 (ID 중복 시 수동 우선).

Expand Down Expand Up @@ -139,7 +139,7 @@ npm run collect:jp # 価格.com (JP, JPY 가격)
- 스펙 정확도 수정: 아이폰16 프로 주사율(60→120Hz), `enrichWithDatasetFallback` merge 방식
- 한/미/일 전체 점검: US/JP 데이터셋 fallback, ja 라벨 중앙화(JA_FIELD_LABELS),
danawa URL 공식소스 유출 차단
- 제품명 로케일 정규화 + 데이터셋 122개 확장, 태블릿 카테고리 신설
- 제품명 로케일 정규화 + 데이터셋 124개 확장(수동), 태블릿 카테고리 신설
- UI: 결과 카드 화이트 리디자인, fit score 바, 어필리에이트 "공식" 제거 + 다이렉트 링크

---
Expand All @@ -151,23 +151,22 @@ npm run collect:jp # 価格.com (JP, JPY 가격)
| 항목 | 내용 |
|------|------|
| `CRON_SECRET` 교체 | 현재 기본값 → `openssl rand -base64 32` → Vercel Production |
| VAPID 키 생성·등록 | `npx web-push generate-vapid-keys` → 3개 키 → WatchButton 노출 확인 |
| VAPID 키 생성·등록 | 로컬 3종 SET. WatchButton은 PriceComparison remount로 노출 경로 복구됨 |
| Supabase 마이그레이션 | 0014_price_history.sql 로컬 적용 완료. 프로덕션은 Vercel 배포 후 `npx supabase db push` |
| 네이버 쇼핑 API 키 설정 | [https://developers.naver.com/apps/](https://developers.naver.com/apps/) → 앱 등록 → 쇼핑 체크 → `NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET` → `AXIS_PRICE_SOURCE=naver` |
| Coupang API (대기) | 누적 15만원 매출 달성 후 파트너스 포털 최종승인 → `COUPANG_ACCESS_KEY` / `COUPANG_SECRET_KEY` → `AXIS_PRICE_SOURCE=coupang` 으로 전환 |

### 기능 비활성화 (API 키 없음)

- `BRAVE_SEARCH_API_KEY` 미설정 → 미등록 제품 웹 검색 폴백 안 됨
- `RESEND_API_KEY` 미설정 → 이메일 가격 알림 안 됨
- `VAPID_*` 미설정 → 푸시 알림 안 됨
- `BRAVE_SEARCH_API_KEY` 의도적 미설정 → 웹 검색 폴백은 검증 보류
- `RESEND_API_KEY` 로컬 SET. 프로덕션 패리티·실발송 QA는 별도 확인
- `VAPID_*` 로컬 SET. 푸시 실동작 QA는 별도 확인

### 미확인

- Sony 한국 URL(`sony.co.kr`) 실제 동작 여부
- LG gram 14/16/17형 containment match 오매핑 가능성
- WatchButton UI 미노출 (VAPID_SUBJECT 관련 가능성)
- WatchButton UI 미노출 (VAPID_SUBJECT 관련 가능성)
- ~~WatchButton UI 미노출~~ → 원인: ResultsView에서 PriceComparison orphan. remount 완료. VAPID 가설 폐기
- Sony 한국 URL(`sony.co.kr`) 실제 동작 여부
- LG gram 14/16/17형 containment match 오매핑 가능성

Expand All @@ -176,7 +175,7 @@ npm run collect:jp # 価格.com (JP, JPY 가격)
추가 기능 빌드 전에 핵심 가정부터 싸게 검증한다. 통과 시에만 빌드 재개.

**핵심 가정 (먼저 검증):**
- A 실가격·이력을 싸게 확보 가능 — 쿠팡 현재가 + 노트북 26 SKU 자체 일별 적재 PoC
- A 실가격·이력을 싸게 확보 가능 — 쿠팡 현재가 + 노트북 28 SKU 자체 일별 적재 PoC
→ **[준비 완료]** `naver-provider.ts` + `coupang-provider.ts` + `price-snapshot` 크론 구현 완료 (2026-06-11).
① 네이버 쇼핑 API 즉시 발급 후 `AXIS_PRICE_SOURCE=naver` 로 활성화 (가격=전국최저가, 구매링크=쿠팡 제휴).
② Coupang 누적 15만원 매출 달성 → 최종승인 → `AXIS_PRICE_SOURCE=coupang` 으로 전환 (env 변경만으로 완료).
Expand Down Expand Up @@ -225,13 +224,14 @@ openssl rand -base64 32 # CRON_SECRET 생성
| `SUPABASE_SERVICE_ROLE_KEY` | 설정됨 | Supabase 서비스 키 |
| `GROQ_API_KEY` | 설정됨 | AI 결정 엔진 |
| `CRON_SECRET` | ✅ 교체 완료 | 2026-06-11 새 키로 교체 |
| `NEXT_PUBLIC_VAPID_PUBLIC_KEY` | ✅ 설정됨 | 푸시 알림 |
| `VAPID_PRIVATE_KEY` | ✅ 설정됨 | 푸시 알림 |
| `VAPID_SUBJECT` | ✅ 설정됨 | 푸시 알림 |
| `NEXT_PUBLIC_VAPID_PUBLIC_KEY` | ✅ 로컬 SET | 푸시 알림 |
| `VAPID_PRIVATE_KEY` | ✅ 로컬 SET | 푸시 알림 |
| `VAPID_SUBJECT` | ✅ 로컬 SET | 푸시 알림 |
| `AXIS_PRICE_SOURCE` | ✅ naver | 네이버 쇼핑 최저가 활성화 |
| `NAVER_CLIENT_ID` | ✅ 설정됨 | 네이버 쇼핑 API |
| `NAVER_CLIENT_SECRET` | ✅ 설정됨 | 네이버 쇼핑 API |
| `BRAVE_SEARCH_API_KEY` | ❌ 미설정 | 웹 검색 폴백 |
| `RESEND_API_KEY` | ❌ 미설정 | 이메일 알림 (D단계) |
| `BRAVE_SEARCH_API_KEY` | ❌ 미설정(보류) | 웹 검색 폴백 |
| `RESEND_API_KEY` | ✅ 로컬 SET / 프로덕션 패리티 미확인 | 이메일 알림 (D단계) |
| `NEXT_PUBLIC_SITE_URL` | ⚠️ 미설정 시 fallback | public=`https://axis-app-beta.vercel.app` · layout local=`http://localhost:3000` |
| `COUPANG_ACCESS_KEY` | ❌ 대기 중 | 쿠팡 파트너스 API (최종승인 후 발급 — 15만원 매출 필요) |
| `COUPANG_SECRET_KEY` | ❌ 대기 중 | 쿠팡 파트너스 API |
33 changes: 19 additions & 14 deletions PROMPT.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Axis — AI 작업 프롬프트

> 배포 목표: 2026-08-15 (베타 → 정식) | 현재: 프로덕션 베타 배포 완료, 사업성 검증 단계
> 문서 기준일: 2026-07-21 · 로컬 브랜치 `local-fix` · 프로덕션 HEAD 재배포 필요

---

## 프로젝트 컨텍스트

너는 **Axis** (전자제품 구매 결정 도구)를 베타에서 정식 배포로 끌어올리는 시니어 엔지니어다.

**프로젝트 위치:** `/Users/minseokchae/Documents/Personal_Project/Axis/`
**프로젝트 위치:** `/Users/minseokchae/Dev/deployed/Axis/`

**프로덕션 URL:** https://axis-app-beta.vercel.app
**상태:** 베타 · 사업성 검증 단계 (한국 · 노트북 · 제휴 집중)
Expand All @@ -25,24 +26,28 @@

---

## 현재 완료 상태 (2026-06-17 기준)
## 현재 완료 상태 (2026-07-21 기준)

### 기능 (완료)
- AI 구매 결정 엔진 + 검증 게이트 (verified / partial / unverified)
- 맞춤 재분석 (userContext — 용도·예산·상황 가중치로 결론 재계산, 캐시 우회)
- 네이버 쇼핑 실시간 최저가 + 자체 일별 가격 이력 적재 (크론)
- 이메일 가격 알림 (Resend) + 웹 푸시 알림 (VAPID, PWA)
- 검증 데이터셋 122개 (스마트폰 55 · 이어폰 18 · 노트북 26 · 태블릿 23)
- 이메일 가격 알림 (Resend) + 웹 푸시 알림 (VAPID, PWA) — **코드 완료**
- 검증 데이터셋 수동 124개 (스마트폰 55 · 이어폰 18 · 노트북 28 · 태블릿 23)
- 다국어 KR/US/JP (제품명 로케일 정규화: canonicalName / nameEn / nameJa)
- SEO 정적 비교 페이지 (`/compare/[slug]`), 비교 결과 캐시 (v8), 클릭 트래킹
- SEO 정적 비교 페이지 (`/compare/[slug]`), 비교 결과 캐시 (v9), 클릭 트래킹
- ResultsView `PriceComparison`/`WatchButton` 재마운트 (2026-07-21, `hidePrices`일 때만 숨김)
- `lib/site-url.ts` 공용 사이트 URL 헬퍼 (공개 fallback = beta 호스트)

### 운영 환경 (설정 완료)
### 운영 환경 (로컬 기준 설정 완료)
- `CRON_SECRET` 교체 완료, VAPID 3종 설정, `AXIS_PRICE_SOURCE=naver` 활성
- `NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET` 설정 완료
- `RESEND_API_KEY` 로컬 SET (프로덕션 패리티는 PM 확인 필요)

### 대기 중
### 대기 중 / 프로덕션 갭
- 쿠팡 파트너스 API: 누적 매출 15만원 달성 후 발급 (`AXIS_PRICE_SOURCE=coupang` env 전환만으로 완료)
- `BRAVE_SEARCH_API_KEY`(웹 검색 폴백), `RESEND_API_KEY`(이메일 알림) 미설정
- `BRAVE_SEARCH_API_KEY`(웹 검색 폴백) 미설정 — **보류** (30일 검증 범위 밖)
- 프로덕션 배포가 seed-only SHA `3b54e0a`에 고정 → **로컬 HEAD 재배포 필요 (PM)**

---

Expand All @@ -54,7 +59,7 @@

- 노트북 추천/비교 콘텐츠 15~20편 색인 (SEO/커뮤니티 유입 검증)
- `/compare/[slug]` 정적 페이지가 verified 등급으로 색인되는지 확인
- 데이터셋: 노트북 26 SKU 우선 정확도 점검 (LG gram 14/16/17형 containment match 오매핑 가능성 확인)
- 데이터셋: 노트북 28 SKU 우선 정확도 점검 (LG gram 14/16/17형 containment match 오매핑 가능성 확인)

### Phase 2: 제휴 전환 추적 (검증 가정 C)

Expand All @@ -64,9 +69,9 @@

### Phase 3: 알림 재방문 검증 (검증 가정 D)

- `RESEND_API_KEY` 설정 후 실알림 활성화
- 로컬 `RESEND_API_KEY` SET 확인됨 → 프로덕션 패리티 확인 후 실알림 활성화
- M2 재방문 >30% 측정
- WatchButton UI 미노출 이슈 확인 (VAPID_SUBJECT 관련 가능성)
- WatchButton: 과거 "VAPID_SUBJECT" 가설 폐기. 실제 원인은 ResultsView에서 `PriceComparison` 미마운트였고, 2026-07-21 재마운트로 로컬 수정 완료. 프로덕션 반영은 HEAD 재배포 후 확인.

### Phase 4: 검증 배지 A/B (검증 가정 E)

Expand All @@ -87,7 +92,7 @@
- **결과/추천 로직은 요청 없이 건드리지 않음.** `selectedOption`, `reasons`, `oneLineConclusion`, per-option 분석은 사용자가 프롬프트로 직접 작업.
- **스펙은 DB에서 꺼내지 않음.** 공식 페이지 AI 검증 또는 `lib/specs/dataset/` 수동 데이터에서만. Supabase는 계정·히스토리·가격추적·알림 전용.
- **검증 게이트 준수.** primary 스펙이 공식 소스(tier 1~2)로 뒷받침될 때만 `verified`. 뻥스펙·하드코딩 fallback 추천 금지. 없는 제품은 "찾을 수 없음"으로 떨어뜨림.
- 스키마·결과 포맷 변경 시 `lib/comparison-cache.ts`의 `CACHE_VERSION`을 올려 구버전 캐시 무효화 (현재 v8).
- 스키마·결과 포맷 변경 시 `lib/comparison-cache.ts`의 `CACHE_VERSION`을 올려 구버전 캐시 무효화 (현재 v9).
- `CRON_SECRET` 등 시크릿을 코드·문서에 하드코딩하지 않음.
- 변경 후 반드시 `npm test` (특히 레지스트리·데이터셋·파이프라인 변경 시) + `npx tsc --noEmit`.

Expand All @@ -113,9 +118,9 @@ components/
lib/
decision-engine.ts 비교 파이프라인 오케스트레이터
ai/ AI 프로바이더 추상화 + 프롬프트
specs/dataset/ 수동 검증 스펙 122개
specs/dataset/ 수동 검증 스펙 124개
pricing/ 가격 프로바이더 (naver · coupang · seed)
comparison-cache.ts 캐시 레이어 (v8)
comparison-cache.ts 캐시 레이어 (v9)
affiliate.ts 제휴 링크 생성 (Amazon/Coupang/Naver)
scripts/collect-specs/ 반자동 스펙 수집 (danawa/gsmarena/kakaku)
```
Expand Down
Loading
Loading