공개 문서
docs/work-log.md
아래는 docs/work-log.md 와 동일한 원문입니다. Markdown과 HTML 변환 결과를 각각 복사할 수 있습니다.
공개 문서 원문 (Markdown)
# saas-login-auth-checkout 작업 로그
> 오류·설계 결정·진행 이력
>
> **경로 표기(2026-05, Phase 12):** 재현·실행은 **Git 루트 `./`** (`cd "$(git rev-parse --show-toplevel)"`). 아래 표의 `CLAUDE/projects/openclaw`, `projects/openclaw`, `saas-engine/…` 등은 **당시 기록·워크스페이스 관례**를 그대로 둔 것이며, 현재 트리·배포 기준은 `docs/deployment-registry.md`를 따른다.
---
## 변경 이력
| 일자 | 내용 |
|------|------|
| 2026-04-26 | **detailpage run-page UX 강화(Do 단계)**: 좌측 패널에 입력 방식 선택(`1. 수집사이트 URL 입력`, `2. 더망고 상품번호 입력`) 추가. 우측 패널에 `다음 단계로 진행하기 위해 수집된 정보를 확인합니다` 문구 + 필수항목 체크박스(입력 방식/기본 입력/itemName/sourceSite) 연동. 필수 충족 시 `1단계완성, 다음단계로 진행해도됩니다.` 노출 로직 반영. |
| 2026-04-26 | **TheMango API 502 원인 추적 및 가드 보강**: `THEMANGO_API_URL` 예시값 우선 선택 이슈 수정(실서비스 URL 우선 + `.example` 제외), TheMango 응답 `code/message`를 실제 오류로 승격해 빈목록 오인 방지. `/api/productList`는 `X-API-SENDER` 필수임을 확인했고 현재 차단 원인은 `WRONG X-API-SENDER HEADER`로 확정. |
| 2026-04-26 | **운영 준비 상태**: `.env.example`에 `THEMANGO_API_SENDER` 변수 추가 및 우선순위 문서화(`sender: THEMANGO_API_SENDER -> THEMANGO_LOGIN_ID -> THEMANGO_SERVICE_IP`). 운영팀에서 허용 sender 값 전달 즉시 반영 후 URL/더망고 모드 최종 체크박스 완료 재검수 예정. |
| 2026-04-19 | **허브 랜딩 소스 복구 + 재배포**: 워크스페이스에 `hub/curriculum/*`·`hub/resources` 페이지와 `CurriculumLanding`이 빠져 프로덕션에서 해당 경로가 404였음 → 파일 재생성, `hub-data-saas-engine` 동기화. Vercel 재배포 `dpl_59nyC97JAkuNWem6GNKrnfnt2Qpo`, 빌드 로그에 `…/curriculum/auth|billing|chat|dashboard`, `…/resources` 확인. *(첫 배포 `38BN…`는 업로드에 신규 라우트 미포함.)* |
| 2026-04-19 | **허브 커리큘럼·리소스 랜딩 일괄**: `CurriculumLanding` 공통 컴포넌트(`components/saas-engine-hub/curriculum-landing.tsx`)로 Auth 리팩터. 신규 라우트 `…/curriculum/billing`·`dashboard`·`chat`, `…/hub/resources`. `lib/hub-data-saas-engine.tsx`에서 pill·사이드바·피드·퀵링크를 랜딩·`#doc-*` 앵커로 정리(결제 pill → 구독은 `#doc-subscriptions`). 상단 도구 Billing → 결제 랜딩. Plan: `docs/plan/features/hub-curriculum-landings-rollout.plan.md`. |
| 2026-04-19 | **허브 Auth 링크 통일**: `도구 & 스택` Auth pill이 `/login`으로 가던 것을 `…/hub/curriculum/auth`로 변경. 사이드바 Auth 하위(Audit·Gate·OAuth·User DB)는 `/saas-files/…` 직링크 대신 **같은 랜딩 + 앵커**(`#doc-audit` 등)로 이동, `auth` 랜딩 `DocCard`에 `id`·`scroll-mt` 부여. `HubSidebar` `ExpandableItem`은 `pathname`+`#` 기준으로 활성·하위 강조(`useHash`+`stripHash`). |
| 2026-04-19 | **대시보드 레이아웃**: 환영·워크스페이스 행이 `lg`에서 우측 열 23rem 고정 + 타임라인 그리드가 뷰포트 `md/xl` 브레이크포인트를 써 좁은 열에서 가로 넘침·우측 치우침 발생 → `max-w-6xl`·`min-w-0`·`overflow-x-hidden`, 2열은 `2xl`부터·우측 `minmax(28rem,1fr)`, `WorkspaceProjectsTimeline`은 `@container` + `@min-[36rem]/[68rem]` 열 분기. 구독 로드 `useEffect`는 eslint(`set-state-in-effect`) 회피용 `queueMicrotask` 래핑. |
| 2026-04-19 | **허브 본문 레이아웃**: `HubLayout`의 `main`에 `w-full`+`lg:ml-[220px]`만 두면 블록 너비가 사이드바 폭만큼 넘쳐 본문이 우측으로 치우쳐 보일 수 있음 → `lg:w-[calc(100%-220px)] lg:max-w-none`로 수정(모든 프로젝트 허브 공통). |
| 2026-04-19 | **허브 Auth 단일 랜딩**: `app/projects/saas-engine/hub/curriculum/auth/page.tsx` 추가(요약 + Auth Audit/Gate/OAuth/User DB Sync → `/saas-files/docs/…`). `lib/hub-data-saas-engine.tsx`에서 커리큘럼 pill·사이드바 부모「인증 (Auth)」href를 `/projects/saas-engine/hub/curriculum/auth`로 통일. Plan: `docs/plan/features/hub-auth-curriculum-landing.plan.md`. |
| 2026-04-19 | **`/chat` 사이드바·본문 불일치**: 다른 대화 클릭 시 `fetchMessages` 완료 전까지 이전 스레드가 남던 문제 → `handleSelectConversation` 에서 즉시 `setMessages([])`, 메시지 로드 `useEffect`에 `AbortController`로 빠른 전환 시 레이스 제거. 사이드바 제목 `/chat`(경로만 검색) → `titleFromChatMessage`에서 기본 제목 처리. |
| 2026-04-19 | **Gemini 404 수정**: 원인은 `gemini-2.0-flash` 가 신규 키에 대해 API에서 더 이상 제공되지 않음. 기본 모델을 **`gemini-2.5-flash`** 로 변경하고, 선호 모델이 **404** 이면 `gemini-2.5-flash` → `gemini-1.5-flash` 순으로 폴백(`features/gemini-chat/service/geminiService.ts`). `.env.example` 안내 갱신. 배포 필요. |
| 2026-04-19 | Vercel `GEMINI_*` 설정 후 **당시 워크스페이스 표기** `projects/openclaw`(≈ Git 루트 **`./`**)에서 `vercel deploy --prod --yes` 로 프로덕션 재배포 완료. `/api/health`에 **`ai.geminiApiKeyConfigured`(불리언만)** 추가해 배포 검증 가능. 고정 별칭 `https://saas-engine-xi.vercel.app/api/health` 에서 `geminiApiKeyConfigured:true` 확인. |
| 2026-04-19 | 환경 변수 정리: *당시 경로 병기* `saas-engine/.env.example`에 `GEMINI_API_KEY`/Supabase/Usage/Polar 템플릿 통합 · `GEMINI_MODEL` 안내 · `USAGE_LIMIT_GEMINI_CHAT_DAILY`(코드 미사용) 제거 · `CLAUDE.md` 환경 변수 섹션 · `openclaw/.env.example` Gemini 줄 보강. `.gitignore`/`git check-ignore`로 `.env.local` 제외·`.env.example` 커밋 가능 확인. |
| 2026-04-19 | **Gemini 채팅 P1**: `GEMINI_MODEL` env(기본 `gemini-2.0-flash`), `/chat` 컴포저 모드 → `applyComposerModePrefix`로 API 전송, `lib/gemini-chat-client` 스트림 공통화, 첫 사용자 메시지 시 `updateConversationTitle`·사이드바 제목 동기화. 허브는 `lib/chat-composer-mode`로 접두 로직 통일. |
| 2026-04-19 | **Gemini 채팅 P0**: API는 `{ error: ChatErrorCode }` 만 노출(`GeminiChatApiError`), `lib/gemini-chat-errors.ts`의 `getChatErrorUserMessage`로 한글 매핑. `/api/chat`·`/api/hub-chat/public`·`geminiService`·`/chat`·허브 훅 연동. 스트림 읽기 실패 시 `stream_failed`, 네트워크 시 `network_error`. |
| 2026-04-19 | `/chat` 다크·카드·사이드바(플랜) + 이어서 허브형 컴포저(rounded-3xl, Search/Think/Canvas, Canvas 옆 **Skoolchef's Choice** 드롭다운 6문장)로 정리. 상단 «스튜디오 채팅» 타이틀·빈 화면 칩/카피 제거. |
| 2026-04-09 | **[인시던트] 코드리뷰 후 Google 로그인 오류 → git checkout으로 복구** — 상세 내용은 아래 「2026-04-09 인시던트」 섹션 참조 |
|------|------|
| 2026-04-08 | Vercel 프로덕션 배포 완료. Deployment: `dpl_Ezhg1BnevLQ2PCFss1fmEaazHzNp`, URL: `https://saas-engine-clojqke5y-elponenotes-projects.vercel.app`, Alias: `https://saas-engine-xi.vercel.app` |
| 2026-04-08 | 템플릿 작업 참조 고정: `docs/hub-template-standard.md` 신설 후 `CLAUDE.md`, `Projects-Status.md`에 **작업 시작 시 필수 참조**로 등록. 이후 메인메뉴 서브페이지 작업은 해당 문서를 먼저 확인하도록 운영 규칙화. |
| 2026-04-08 | 허브 템플릿 통일 확장: `ai-coding-tools` 허브를 리다이렉트형에서 Bento 허브 컴포넌트형으로 재작성. `ai-coding-tools-files` 파일 라우트와 커맨드 팔레트 인덱스 추가. |
| 2026-04-08 | 추가 실DB 마이그레이션 완료(`nuqjwsxdscxyhbrsmiqd`): `public.conversations`, `public.messages`, `public.usage`, `increment_usage` 함수 적용. 대시보드/채팅에서 `schema cache` 테이블 누락 오류(`usage`, `conversations`) 해소. |
| 2026-04-08 | Supabase 실DB 마이그레이션 적용 완료(`nuqjwsxdscxyhbrsmiqd`): `public.users` + 트리거, 기존 auth 사용자 동기화, `public.subscriptions` 생성 및 RLS/정책 반영. 대시보드 구독 테이블 누락 오류 해소. |
| 2026-04-08 | DB 폴백 복구: `public.subscriptions` 미생성 환경에서 `/api/subscription`이 500을 내지 않고 `free/none`으로 안전 폴백하도록 수정. 대시보드가 로그인 직후 오류로 깨지는 현상 완화. |
| 2026-04-08 | OAuth 리다이렉트 호스트 고정: `engine/auth/auth.ts`의 `getOAuthCallbackUrl()`가 브라우저 현재 origin 대신 `NEXT_PUBLIC_APP_URL`을 우선 사용하도록 수정. `127.0.0.1`/`localhost`/LAN IP 혼용으로 인한 external code exchange 실패 재발 방지. |
| 2026-04-08 | 비즈니스 정보 업데이트: 회사명 `스쿨코리아`, 사업자등록번호 `603-49-91712`를 카카오 운영 메타데이터로 반영. `.env.local`에 `KAKAO_BIZ_COMPANY_NAME`, `KAKAO_BIZ_REGISTRATION_NUMBER` 추가, `.env.example` 템플릿 동기화. |
| 2026-04-08 | 카카오 콘솔 설정 상태 업데이트: **카카오 로그인 활성화 완료**, **동의항목 설정 완료**로 확인. 현재 검증 단계는 앱에서 `/login` → `Kakao Login` 클릭 후 `/auth/callback` 경유 로그인 성공 여부 확인으로 전환. |
| 2026-04-08 | 카카오 앱 메타정보 확정 기록: 앱 ID `1425534`, 앱 타입 `비즈 앱`, 앱명 `saas-engine`. `.env.local`에 참조 키(`KAKAO_APP_NAME`, `KAKAO_APP_TYPE`) 추가해 운영 컨텍스트를 명시. |
| 2026-04-08 | 카카오 Redirect URI 확정 반영: `https://nuqjwsxdscxyhbrsmiqd.supabase.co/auth/v1/callback`. `.env.local`에 `KAKAO_REDIRECT_URI` 추가, `.env.example` 템플릿 및 OAuth 설정 문서(`docs/supabase-google-oauth-setup.md`) 동기화. |
| 2026-04-08 | 카카오 로그인 연동 정보 반영: `.env.local`에 `KAKAO_APP_ID=1425534`, `KAKAO_REST_API_KEY` 입력. 보안상 문서에는 REST 키 원문 미기록(마스킹: `012f...4fde`). 이후 점검은 Supabase Dashboard의 Kakao Provider(Client ID/Secret) 설정 + Redirect URI(`https://<project-ref>.supabase.co/auth/v1/callback`) 확인 기준으로 진행. |
| 2026-04-08 | Next.js 16 경고 대응: 루트와 `apps/gemini-saas`의 `middleware.ts`를 `proxy.ts`로 마이그레이션(`proxy` 함수 + `config.matcher` 유지). 빌드에서 deprecation 경고 제거 확인. |
| 2026-04-08 | 운영 안정화: `docs/dashboard-qa-checklist.md` 추가(인증/무료/유료/오류/링크 A~E 시나리오). `dashboard-update-routine.md`, `Projects-Status.md`에서 체크리스트 참조 연결. |
| 2026-04-08 | 대시보드 P3: 공통 UI 정렬. `app/dashboard/page.tsx`의 「시작하기/구독/계정」과 `usage-summary-section.tsx`를 `Card` + `CardHeader` + `CardContent` 기반으로 리팩터링해 카드 스타일을 일관화. |
| 2026-04-08 | 대시보드 P2: `UsageSummarySection`(`GET /api/usage` 진행 바·재시도·채팅 링크), 「시작하기」에 `DashboardTimeline`+`buildDashboardTimelineSteps` 배치. `dashboard-timeline`에서 무료 사용자도 AI 채팅 단계를 `current`로 두고 한도 안내 문구로 수정. |
| 2026-04-08 | 대시보드 P1: `/api/health`에 `oauthProviderHint` 추가, 로그인·대시보드에 동일 OAuth 안내 배너. 대시보드에서 env 누락 경고, 구독 API `hint` 표시·「다시 불러오기」, 무료/미구독(`free`·`none`) 빈 상태 카피. 구독 로드는 `queueMicrotask`+취소 플래그로 `set-state-in-effect` 린트 회피. |
| 2026-04-08 | 대시보드 진행 상황 정리: `docs/dashboard-update-routine.md` 추가(스냅샷·매일 체크리스트·개선 백로그). `Projects-Status.md` 경로를 당시 표기 `projects/openclaw/saas-engine`(이력상·폐기 경로) 기준으로 정정했으며, 현재 Git 루트는 **`./`** *(당시 기록: `CLAUDE/projects/openclaw`)* 에서 작업, `docs/progress-saas-engine.md` 결제·구독·WALL3 표를 현재 코드와 맞춤. |
| 2026-04-07 | 자동 재연결 실행: Supabase MCP로 `saas-engine` 프로젝트 URL/anon key 복구 후 `.env.local` 반영, `/api/health`에서 `auth.ready=true` 확인. 브라우저 OAuth 클릭까지 검증했으나 Supabase Auth 로그에 **`provider is not enabled`**(GET `/authorize` 400) 확인되어 대시보드에서 Google/Kakao Provider 활성화가 추가 필요. |
| 2026-04-07 | 로그인 재연결 점검: **Git 루트 `./`** *(당시 기록: `CLAUDE/projects/openclaw`)* 의 `.env.local`(이력상 표기 `openclaw/saas-engine`)이 비어 있어 OAuth가 끊긴 원인 확인. `GET /api/health`에 `auth.missingEnvKeys` 추가, `/login`에 누락 env 경고 UI 추가, 운영 문서에 포트별 Redirect URL 점검 절차 반영. |
| 2026-04-07 | usage 엔진을 **플랜 연동 한도**로 확장: `subscriptions(plan,status)`를 조회해 active/trialing일 때만 pro/paid 한도 적용, 그 외는 free 한도 적용. `/api/usage`에 `allowed`, `remaining` 응답 추가. |
| 2026-03-11 | 방법 A 진행: .env에 NOTION_TOKEN 설정 후 fetch-notion-ref 재실행 → 동일 404. **SaaS 페이지를 Notion Integration에 공유해야 API 접근 가능** |
| 2026-03-11 | Notion API로 SaaS 페이지 추출 시도 → 페이지가 Integration에 공유되지 않아 404. 레퍼런스 안내 파일 `docs/reference/notion-saas.md` 추가 |
| 2026-03-11 | 프로젝트 생성 (로그인·인증·체크아웃 플로우) |
---
## 오류 & 원인 & 해결
(기록 예정)
---
## 아키텍처·설계 결정
(기록 예정)
---
## 2026-04-09 인시던트: 코드리뷰 후 Google 로그인 오류
### 요약
코드리뷰(bkit:code-analyzer) 수행 중 두 가지 잘못된 수정이 Google 로그인을 끊었음. `git checkout`으로 원복 후 정상화.
### 원인 1 — `middleware.ts` 생성 (서버 충돌)
- **상황**: 코드리뷰에서 "middleware.ts가 없어 auth gate가 dead code"라고 지적 → `middleware.ts` 신규 생성
- **실제 문제**: Next.js 16은 `middleware.ts` 대신 `proxy.ts`를 미들웨어 파일로 사용. 두 파일이 공존하면 서버가 시작 시 `Unhandled Rejection: Both middleware.ts and proxy.ts detected` 오류 루프 발생
- **교훈**: Next.js 16에서는 **`proxy.ts`가 곧 middleware**. `middleware.ts`를 별도로 만들 필요 없음. 이미 `proxy.ts`에 `export async function proxy()` + `export const config`가 있으면 완전히 작동함
### 원인 2 — `app/auth/callback/page.tsx`에서 `getSession()` → `getUser()` 변경
- **상황**: 보안 개선 목적으로 cookie 기반 `getSession()` → 서버 검증 `getUser()`로 변경
- **실제 문제**: OAuth PKCE 플로우에서 `/auth/callback` 페이지 로드 시점에 Supabase 브라우저 클라이언트(`createBrowserClient`)는 URL의 `?code=` 파라미터를 **`getSession()` 호출 시 자동으로 토큰 교환**함. `getUser()`는 이미 수립된 세션을 검증하는 용도라서, 코드 교환 전에 실행되면 항상 `null` 반환 → 매번 `/login`으로 리디렉트
- **교훈**: 컨텍스트별 올바른 함수:
| 위치 | 함수 | 이유 |
|------|------|------|
| `proxy.ts` (서버 미들웨어) | `getUser()` | 서버에서 JWT 직접 검증 |
| `app/auth/callback/page.tsx` (클라이언트) | `getSession()` | PKCE 코드 교환 트리거 역할 |
| `app/dashboard` 등 일반 페이지 | `getCurrentUser()` / `getUser()` | 세션 수립 후이므로 안전 |
### 복구 방법
```bash
# 당시 트리 병기 경로; 현재 앱 루트·`saas-engine/` 역할은 `docs/deployment-registry.md` 참고
git checkout -- saas-engine/app/api/webhooks/polar/route.ts saas-engine/app/auth/callback/page.tsx
rm saas-engine/middleware.ts # Next.js 16에서 proxy.ts와 충돌
```
### 이후 재발 방지 규칙
1. **`proxy.ts` 수정 금지**: Next.js 16 미들웨어 파일. `middleware.ts`를 별도 생성하면 충돌
2. **`app/auth/callback/page.tsx`의 `getSession()` 유지**: OAuth 콜백 클라이언트에서는 `getSession()`이 올바른 패턴
3. **코드리뷰 후 즉시 로그인 테스트**: auth 관련 파일 수정 시 반드시 Google 로그인 플로우 end-to-end 확인
공개 문서 변환 코드 (HTML)
<h1>saas-login-auth-checkout 작업 로그</h1>
<blockquote>
<p>오류·설계 결정·진행 이력</p>
<p><strong>경로 표기(2026-05, Phase 12):</strong> 재현·실행은 <strong>Git 루트 <code>./</code></strong> (<code>cd "$(git rev-parse --show-toplevel)"</code>). 아래 표의 <code>CLAUDE/projects/openclaw</code>, <code>projects/openclaw</code>, <code>saas-engine/…</code> 등은 <strong>당시 기록·워크스페이스 관례</strong>를 그대로 둔 것이며, 현재 트리·배포 기준은 <code>docs/deployment-registry.md</code>를 따른다.</p>
</blockquote>
<hr>
<h2>변경 이력</h2>
<table>
<thead>
<tr>
<th>일자</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>2026-04-26</td>
<td><strong>detailpage run-page UX 강화(Do 단계)</strong>: 좌측 패널에 입력 방식 선택(<code>1. 수집사이트 URL 입력</code>, <code>2. 더망고 상품번호 입력</code>) 추가. 우측 패널에 <code>다음 단계로 진행하기 위해 수집된 정보를 확인합니다</code> 문구 + 필수항목 체크박스(입력 방식/기본 입력/itemName/sourceSite) 연동. 필수 충족 시 <code>1단계완성, 다음단계로 진행해도됩니다.</code> 노출 로직 반영.</td>
</tr>
<tr>
<td>2026-04-26</td>
<td><strong>TheMango API 502 원인 추적 및 가드 보강</strong>: <code>THEMANGO_API_URL</code> 예시값 우선 선택 이슈 수정(실서비스 URL 우선 + <code>.example</code> 제외), TheMango 응답 <code>code/message</code>를 실제 오류로 승격해 빈목록 오인 방지. <code>/api/productList</code>는 <code>X-API-SENDER</code> 필수임을 확인했고 현재 차단 원인은 <code>WRONG X-API-SENDER HEADER</code>로 확정.</td>
</tr>
<tr>
<td>2026-04-26</td>
<td><strong>운영 준비 상태</strong>: <code>.env.example</code>에 <code>THEMANGO_API_SENDER</code> 변수 추가 및 우선순위 문서화(<code>sender: THEMANGO_API_SENDER -> THEMANGO_LOGIN_ID -> THEMANGO_SERVICE_IP</code>). 운영팀에서 허용 sender 값 전달 즉시 반영 후 URL/더망고 모드 최종 체크박스 완료 재검수 예정.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>허브 랜딩 소스 복구 + 재배포</strong>: 워크스페이스에 <code>hub/curriculum/*</code>·<code>hub/resources</code> 페이지와 <code>CurriculumLanding</code>이 빠져 프로덕션에서 해당 경로가 404였음 → 파일 재생성, <code>hub-data-saas-engine</code> 동기화. Vercel 재배포 <code>dpl_59nyC97JAkuNWem6GNKrnfnt2Qpo</code>, 빌드 로그에 `…/curriculum/auth</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>허브 커리큘럼·리소스 랜딩 일괄</strong>: <code>CurriculumLanding</code> 공통 컴포넌트(<code>components/saas-engine-hub/curriculum-landing.tsx</code>)로 Auth 리팩터. 신규 라우트 <code>…/curriculum/billing</code>·<code>dashboard</code>·<code>chat</code>, <code>…/hub/resources</code>. <code>lib/hub-data-saas-engine.tsx</code>에서 pill·사이드바·피드·퀵링크를 랜딩·<code>#doc-*</code> 앵커로 정리(결제 pill → 구독은 <code>#doc-subscriptions</code>). 상단 도구 Billing → 결제 랜딩. Plan: <code>docs/plan/features/hub-curriculum-landings-rollout.plan.md</code>.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>허브 Auth 링크 통일</strong>: <code>도구 & 스택</code> Auth pill이 <code>/login</code>으로 가던 것을 <code>…/hub/curriculum/auth</code>로 변경. 사이드바 Auth 하위(Audit·Gate·OAuth·User DB)는 <code>/saas-files/…</code> 직링크 대신 <strong>같은 랜딩 + 앵커</strong>(<code>#doc-audit</code> 등)로 이동, <code>auth</code> 랜딩 <code>DocCard</code>에 <code>id</code>·<code>scroll-mt</code> 부여. <code>HubSidebar</code> <code>ExpandableItem</code>은 <code>pathname</code>+<code>#</code> 기준으로 활성·하위 강조(<code>useHash</code>+<code>stripHash</code>).</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>대시보드 레이아웃</strong>: 환영·워크스페이스 행이 <code>lg</code>에서 우측 열 23rem 고정 + 타임라인 그리드가 뷰포트 <code>md/xl</code> 브레이크포인트를 써 좁은 열에서 가로 넘침·우측 치우침 발생 → <code>max-w-6xl</code>·<code>min-w-0</code>·<code>overflow-x-hidden</code>, 2열은 <code>2xl</code>부터·우측 <code>minmax(28rem,1fr)</code>, <code>WorkspaceProjectsTimeline</code>은 <code>@container</code> + <code>@min-[36rem]/[68rem]</code> 열 분기. 구독 로드 <code>useEffect</code>는 eslint(<code>set-state-in-effect</code>) 회피용 <code>queueMicrotask</code> 래핑.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>허브 본문 레이아웃</strong>: <code>HubLayout</code>의 <code>main</code>에 <code>w-full</code>+<code>lg:ml-[220px]</code>만 두면 블록 너비가 사이드바 폭만큼 넘쳐 본문이 우측으로 치우쳐 보일 수 있음 → <code>lg:w-[calc(100%-220px)] lg:max-w-none</code>로 수정(모든 프로젝트 허브 공통).</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>허브 Auth 단일 랜딩</strong>: <code>app/projects/saas-engine/hub/curriculum/auth/page.tsx</code> 추가(요약 + Auth Audit/Gate/OAuth/User DB Sync → <code>/saas-files/docs/…</code>). <code>lib/hub-data-saas-engine.tsx</code>에서 커리큘럼 pill·사이드바 부모「인증 (Auth)」href를 <code>/projects/saas-engine/hub/curriculum/auth</code>로 통일. Plan: <code>docs/plan/features/hub-auth-curriculum-landing.plan.md</code>.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong><code>/chat</code> 사이드바·본문 불일치</strong>: 다른 대화 클릭 시 <code>fetchMessages</code> 완료 전까지 이전 스레드가 남던 문제 → <code>handleSelectConversation</code> 에서 즉시 <code>setMessages([])</code>, 메시지 로드 <code>useEffect</code>에 <code>AbortController</code>로 빠른 전환 시 레이스 제거. 사이드바 제목 <code>/chat</code>(경로만 검색) → <code>titleFromChatMessage</code>에서 기본 제목 처리.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>Gemini 404 수정</strong>: 원인은 <code>gemini-2.0-flash</code> 가 신규 키에 대해 API에서 더 이상 제공되지 않음. 기본 모델을 <strong><code>gemini-2.5-flash</code></strong> 로 변경하고, 선호 모델이 <strong>404</strong> 이면 <code>gemini-2.5-flash</code> → <code>gemini-1.5-flash</code> 순으로 폴백(<code>features/gemini-chat/service/geminiService.ts</code>). <code>.env.example</code> 안내 갱신. 배포 필요.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td>Vercel <code>GEMINI_*</code> 설정 후 <strong>당시 워크스페이스 표기</strong> <code>projects/openclaw</code>(≈ Git 루트 <strong><code>./</code></strong>)에서 <code>vercel deploy --prod --yes</code> 로 프로덕션 재배포 완료. <code>/api/health</code>에 <strong><code>ai.geminiApiKeyConfigured</code>(불리언만)</strong> 추가해 배포 검증 가능. 고정 별칭 <code>https://saas-engine-xi.vercel.app/api/health</code> 에서 <code>geminiApiKeyConfigured:true</code> 확인.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td>환경 변수 정리: <em>당시 경로 병기</em> <code>saas-engine/.env.example</code>에 <code>GEMINI_API_KEY</code>/Supabase/Usage/Polar 템플릿 통합 · <code>GEMINI_MODEL</code> 안내 · <code>USAGE_LIMIT_GEMINI_CHAT_DAILY</code>(코드 미사용) 제거 · <code>CLAUDE.md</code> 환경 변수 섹션 · <code>openclaw/.env.example</code> Gemini 줄 보강. <code>.gitignore</code>/<code>git check-ignore</code>로 <code>.env.local</code> 제외·<code>.env.example</code> 커밋 가능 확인.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>Gemini 채팅 P1</strong>: <code>GEMINI_MODEL</code> env(기본 <code>gemini-2.0-flash</code>), <code>/chat</code> 컴포저 모드 → <code>applyComposerModePrefix</code>로 API 전송, <code>lib/gemini-chat-client</code> 스트림 공통화, 첫 사용자 메시지 시 <code>updateConversationTitle</code>·사이드바 제목 동기화. 허브는 <code>lib/chat-composer-mode</code>로 접두 로직 통일.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><strong>Gemini 채팅 P0</strong>: API는 <code>{ error: ChatErrorCode }</code> 만 노출(<code>GeminiChatApiError</code>), <code>lib/gemini-chat-errors.ts</code>의 <code>getChatErrorUserMessage</code>로 한글 매핑. <code>/api/chat</code>·<code>/api/hub-chat/public</code>·<code>geminiService</code>·<code>/chat</code>·허브 훅 연동. 스트림 읽기 실패 시 <code>stream_failed</code>, 네트워크 시 <code>network_error</code>.</td>
</tr>
<tr>
<td>2026-04-19</td>
<td><code>/chat</code> 다크·카드·사이드바(플랜) + 이어서 허브형 컴포저(rounded-3xl, Search/Think/Canvas, Canvas 옆 <strong>Skoolchef's Choice</strong> 드롭다운 6문장)로 정리. 상단 «스튜디오 채팅» 타이틀·빈 화면 칩/카피 제거.</td>
</tr>
<tr>
<td>2026-04-09</td>
<td><strong>[인시던트] 코드리뷰 후 Google 로그인 오류 → git checkout으로 복구</strong> — 상세 내용은 아래 「2026-04-09 인시던트」 섹션 참조</td>
</tr>
<tr>
<td>------</td>
<td>------</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>Vercel 프로덕션 배포 완료. Deployment: <code>dpl_Ezhg1BnevLQ2PCFss1fmEaazHzNp</code>, URL: <code>https://saas-engine-clojqke5y-elponenotes-projects.vercel.app</code>, Alias: <code>https://saas-engine-xi.vercel.app</code></td>
</tr>
<tr>
<td>2026-04-08</td>
<td>템플릿 작업 참조 고정: <code>docs/hub-template-standard.md</code> 신설 후 <code>CLAUDE.md</code>, <code>Projects-Status.md</code>에 <strong>작업 시작 시 필수 참조</strong>로 등록. 이후 메인메뉴 서브페이지 작업은 해당 문서를 먼저 확인하도록 운영 규칙화.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>허브 템플릿 통일 확장: <code>ai-coding-tools</code> 허브를 리다이렉트형에서 Bento 허브 컴포넌트형으로 재작성. <code>ai-coding-tools-files</code> 파일 라우트와 커맨드 팔레트 인덱스 추가.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>추가 실DB 마이그레이션 완료(<code>nuqjwsxdscxyhbrsmiqd</code>): <code>public.conversations</code>, <code>public.messages</code>, <code>public.usage</code>, <code>increment_usage</code> 함수 적용. 대시보드/채팅에서 <code>schema cache</code> 테이블 누락 오류(<code>usage</code>, <code>conversations</code>) 해소.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>Supabase 실DB 마이그레이션 적용 완료(<code>nuqjwsxdscxyhbrsmiqd</code>): <code>public.users</code> + 트리거, 기존 auth 사용자 동기화, <code>public.subscriptions</code> 생성 및 RLS/정책 반영. 대시보드 구독 테이블 누락 오류 해소.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>DB 폴백 복구: <code>public.subscriptions</code> 미생성 환경에서 <code>/api/subscription</code>이 500을 내지 않고 <code>free/none</code>으로 안전 폴백하도록 수정. 대시보드가 로그인 직후 오류로 깨지는 현상 완화.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>OAuth 리다이렉트 호스트 고정: <code>engine/auth/auth.ts</code>의 <code>getOAuthCallbackUrl()</code>가 브라우저 현재 origin 대신 <code>NEXT_PUBLIC_APP_URL</code>을 우선 사용하도록 수정. <code>127.0.0.1</code>/<code>localhost</code>/LAN IP 혼용으로 인한 external code exchange 실패 재발 방지.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>비즈니스 정보 업데이트: 회사명 <code>스쿨코리아</code>, 사업자등록번호 <code>603-49-91712</code>를 카카오 운영 메타데이터로 반영. <code>.env.local</code>에 <code>KAKAO_BIZ_COMPANY_NAME</code>, <code>KAKAO_BIZ_REGISTRATION_NUMBER</code> 추가, <code>.env.example</code> 템플릿 동기화.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>카카오 콘솔 설정 상태 업데이트: <strong>카카오 로그인 활성화 완료</strong>, <strong>동의항목 설정 완료</strong>로 확인. 현재 검증 단계는 앱에서 <code>/login</code> → <code>Kakao Login</code> 클릭 후 <code>/auth/callback</code> 경유 로그인 성공 여부 확인으로 전환.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>카카오 앱 메타정보 확정 기록: 앱 ID <code>1425534</code>, 앱 타입 <code>비즈 앱</code>, 앱명 <code>saas-engine</code>. <code>.env.local</code>에 참조 키(<code>KAKAO_APP_NAME</code>, <code>KAKAO_APP_TYPE</code>) 추가해 운영 컨텍스트를 명시.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>카카오 Redirect URI 확정 반영: <code>https://nuqjwsxdscxyhbrsmiqd.supabase.co/auth/v1/callback</code>. <code>.env.local</code>에 <code>KAKAO_REDIRECT_URI</code> 추가, <code>.env.example</code> 템플릿 및 OAuth 설정 문서(<code>docs/supabase-google-oauth-setup.md</code>) 동기화.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>카카오 로그인 연동 정보 반영: <code>.env.local</code>에 <code>KAKAO_APP_ID=1425534</code>, <code>KAKAO_REST_API_KEY</code> 입력. 보안상 문서에는 REST 키 원문 미기록(마스킹: <code>012f...4fde</code>). 이후 점검은 Supabase Dashboard의 Kakao Provider(Client ID/Secret) 설정 + Redirect URI(<code>https://<project-ref>.supabase.co/auth/v1/callback</code>) 확인 기준으로 진행.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>Next.js 16 경고 대응: 루트와 <code>apps/gemini-saas</code>의 <code>middleware.ts</code>를 <code>proxy.ts</code>로 마이그레이션(<code>proxy</code> 함수 + <code>config.matcher</code> 유지). 빌드에서 deprecation 경고 제거 확인.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>운영 안정화: <code>docs/dashboard-qa-checklist.md</code> 추가(인증/무료/유료/오류/링크 A~E 시나리오). <code>dashboard-update-routine.md</code>, <code>Projects-Status.md</code>에서 체크리스트 참조 연결.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>대시보드 P3: 공통 UI 정렬. <code>app/dashboard/page.tsx</code>의 「시작하기/구독/계정」과 <code>usage-summary-section.tsx</code>를 <code>Card</code> + <code>CardHeader</code> + <code>CardContent</code> 기반으로 리팩터링해 카드 스타일을 일관화.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>대시보드 P2: <code>UsageSummarySection</code>(<code>GET /api/usage</code> 진행 바·재시도·채팅 링크), 「시작하기」에 <code>DashboardTimeline</code>+<code>buildDashboardTimelineSteps</code> 배치. <code>dashboard-timeline</code>에서 무료 사용자도 AI 채팅 단계를 <code>current</code>로 두고 한도 안내 문구로 수정.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>대시보드 P1: <code>/api/health</code>에 <code>oauthProviderHint</code> 추가, 로그인·대시보드에 동일 OAuth 안내 배너. 대시보드에서 env 누락 경고, 구독 API <code>hint</code> 표시·「다시 불러오기」, 무료/미구독(<code>free</code>·<code>none</code>) 빈 상태 카피. 구독 로드는 <code>queueMicrotask</code>+취소 플래그로 <code>set-state-in-effect</code> 린트 회피.</td>
</tr>
<tr>
<td>2026-04-08</td>
<td>대시보드 진행 상황 정리: <code>docs/dashboard-update-routine.md</code> 추가(스냅샷·매일 체크리스트·개선 백로그). <code>Projects-Status.md</code> 경로를 당시 표기 <code>projects/openclaw/saas-engine</code>(이력상·폐기 경로) 기준으로 정정했으며, 현재 Git 루트는 <strong><code>./</code></strong> <em>(당시 기록: <code>CLAUDE/projects/openclaw</code>)</em> 에서 작업, <code>docs/progress-saas-engine.md</code> 결제·구독·WALL3 표를 현재 코드와 맞춤.</td>
</tr>
<tr>
<td>2026-04-07</td>
<td>자동 재연결 실행: Supabase MCP로 <code>saas-engine</code> 프로젝트 URL/anon key 복구 후 <code>.env.local</code> 반영, <code>/api/health</code>에서 <code>auth.ready=true</code> 확인. 브라우저 OAuth 클릭까지 검증했으나 Supabase Auth 로그에 <strong><code>provider is not enabled</code></strong>(GET <code>/authorize</code> 400) 확인되어 대시보드에서 Google/Kakao Provider 활성화가 추가 필요.</td>
</tr>
<tr>
<td>2026-04-07</td>
<td>로그인 재연결 점검: <strong>Git 루트 <code>./</code></strong> <em>(당시 기록: <code>CLAUDE/projects/openclaw</code>)</em> 의 <code>.env.local</code>(이력상 표기 <code>openclaw/saas-engine</code>)이 비어 있어 OAuth가 끊긴 원인 확인. <code>GET /api/health</code>에 <code>auth.missingEnvKeys</code> 추가, <code>/login</code>에 누락 env 경고 UI 추가, 운영 문서에 포트별 Redirect URL 점검 절차 반영.</td>
</tr>
<tr>
<td>2026-04-07</td>
<td>usage 엔진을 <strong>플랜 연동 한도</strong>로 확장: <code>subscriptions(plan,status)</code>를 조회해 active/trialing일 때만 pro/paid 한도 적용, 그 외는 free 한도 적용. <code>/api/usage</code>에 <code>allowed</code>, <code>remaining</code> 응답 추가.</td>
</tr>
<tr>
<td>2026-03-11</td>
<td>방법 A 진행: .env에 NOTION_TOKEN 설정 후 fetch-notion-ref 재실행 → 동일 404. <strong>SaaS 페이지를 Notion Integration에 공유해야 API 접근 가능</strong></td>
</tr>
<tr>
<td>2026-03-11</td>
<td>Notion API로 SaaS 페이지 추출 시도 → 페이지가 Integration에 공유되지 않아 404. 레퍼런스 안내 파일 <code>docs/reference/notion-saas.md</code> 추가</td>
</tr>
<tr>
<td>2026-03-11</td>
<td>프로젝트 생성 (로그인·인증·체크아웃 플로우)</td>
</tr>
</tbody></table>
<hr>
<h2>오류 & 원인 & 해결</h2>
<p>(기록 예정)</p>
<hr>
<h2>아키텍처·설계 결정</h2>
<p>(기록 예정)</p>
<hr>
<h2>2026-04-09 인시던트: 코드리뷰 후 Google 로그인 오류</h2>
<h3>요약</h3>
<p>코드리뷰(bkit:code-analyzer) 수행 중 두 가지 잘못된 수정이 Google 로그인을 끊었음. <code>git checkout</code>으로 원복 후 정상화.</p>
<h3>원인 1 — <code>middleware.ts</code> 생성 (서버 충돌)</h3>
<ul>
<li><strong>상황</strong>: 코드리뷰에서 "middleware.ts가 없어 auth gate가 dead code"라고 지적 → <code>middleware.ts</code> 신규 생성</li>
<li><strong>실제 문제</strong>: Next.js 16은 <code>middleware.ts</code> 대신 <code>proxy.ts</code>를 미들웨어 파일로 사용. 두 파일이 공존하면 서버가 시작 시 <code>Unhandled Rejection: Both middleware.ts and proxy.ts detected</code> 오류 루프 발생</li>
<li><strong>교훈</strong>: Next.js 16에서는 <strong><code>proxy.ts</code>가 곧 middleware</strong>. <code>middleware.ts</code>를 별도로 만들 필요 없음. 이미 <code>proxy.ts</code>에 <code>export async function proxy()</code> + <code>export const config</code>가 있으면 완전히 작동함</li>
</ul>
<h3>원인 2 — <code>app/auth/callback/page.tsx</code>에서 <code>getSession()</code> → <code>getUser()</code> 변경</h3>
<ul>
<li><strong>상황</strong>: 보안 개선 목적으로 cookie 기반 <code>getSession()</code> → 서버 검증 <code>getUser()</code>로 변경</li>
<li><strong>실제 문제</strong>: OAuth PKCE 플로우에서 <code>/auth/callback</code> 페이지 로드 시점에 Supabase 브라우저 클라이언트(<code>createBrowserClient</code>)는 URL의 <code>?code=</code> 파라미터를 <strong><code>getSession()</code> 호출 시 자동으로 토큰 교환</strong>함. <code>getUser()</code>는 이미 수립된 세션을 검증하는 용도라서, 코드 교환 전에 실행되면 항상 <code>null</code> 반환 → 매번 <code>/login</code>으로 리디렉트</li>
<li><strong>교훈</strong>: 컨텍스트별 올바른 함수:<table>
<thead>
<tr>
<th>위치</th>
<th>함수</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td><code>proxy.ts</code> (서버 미들웨어)</td>
<td><code>getUser()</code></td>
<td>서버에서 JWT 직접 검증</td>
</tr>
<tr>
<td><code>app/auth/callback/page.tsx</code> (클라이언트)</td>
<td><code>getSession()</code></td>
<td>PKCE 코드 교환 트리거 역할</td>
</tr>
<tr>
<td><code>app/dashboard</code> 등 일반 페이지</td>
<td><code>getCurrentUser()</code> / <code>getUser()</code></td>
<td>세션 수립 후이므로 안전</td>
</tr>
</tbody></table>
</li>
</ul>
<h3>복구 방법</h3>
<pre><code class="language-bash"># 당시 트리 병기 경로; 현재 앱 루트·`saas-engine/` 역할은 `docs/deployment-registry.md` 참고
git checkout -- saas-engine/app/api/webhooks/polar/route.ts saas-engine/app/auth/callback/page.tsx
rm saas-engine/middleware.ts # Next.js 16에서 proxy.ts와 충돌
</code></pre>
<h3>이후 재발 방지 규칙</h3>
<ol>
<li><strong><code>proxy.ts</code> 수정 금지</strong>: Next.js 16 미들웨어 파일. <code>middleware.ts</code>를 별도 생성하면 충돌</li>
<li><strong><code>app/auth/callback/page.tsx</code>의 <code>getSession()</code> 유지</strong>: OAuth 콜백 클라이언트에서는 <code>getSession()</code>이 올바른 패턴</li>
<li><strong>코드리뷰 후 즉시 로그인 테스트</strong>: auth 관련 파일 수정 시 반드시 Google 로그인 플로우 end-to-end 확인</li>
</ol>