---
title: "핸드오프 — 코칭AI 커스텀(설정 통합·코치챗 설정 CRUD) + app_settings 치명적 저장버그 수정"
category: "handoff"
document_type: "핸드오프"
source_status: "generated"
knowledge_group: "03_history"
priority: "High"
purpose: "코치챗을 설정 CRUD(신발·운동종목·런타입·개인메모)와 코칭AI 설정 통합 페이지로 확장(PR#65~72)하고, 그 과정에서 발견한 app_settings 치명적 저장버그(onConflict:'key' ↔ (user_id,key) 복합 unique 불일치로 앱 전반 무음 저장실패)를 전면 수정한 세션의 다음 스레드 인계 핸드오프. MCP 라이브(오너계정) 검증 완료. 근본원인·교훈·기술상태·인계 작업후보 포함."
read_when: ["코치노트AI","최신상태복구","DB·RLS·보안"]
updated: "2026-06-09"
work_timestamp: "20260609_185623"
context: "달록본레포CC (D:\\dallog\\dallog_git) — 코칭AI 커스텀·설정 CRUD·app_settings 저장버그(PR#65~72) 인계"
source_of_truth: "https://dallog-tools.hansbridge.co.kr/knowledge/"
---
# 핸드오프 — 코칭AI 커스텀 + app_settings 치명적 저장버그 수정

## 한 줄 요약

코치챗을 "유저가 직접 하는 설정을 말로 대신"하게 확장(설정 통합 페이지 + 신발·운동종목·런타입·개인메모 CRUD)했고, 그 과정에서 **앱 전반의 app_settings 저장이 원래부터 깨져 있던 치명적 버그(onConflict:'key' ↔ (user_id,key) 복합 unique 불일치)를 발견·전면 수정**했다. 전부 MCP 라이브(오너계정)로 검증 완료. 본레포 git 미접촉(개발 PR만).

## 이번 세션 완료 (PR, 전부 main 머지 + 자동배포)

| PR | 내용 |
|---|---|
| #65 | chat-proxy: GPT 자동 폴백 차단(플랜 경제성) + provider 결제에러 사용자 노출 차단(친절 안내) |
| #66 | 설정 6개 목록 기본 접힘(신발·런타입·운동종목·저장루틴·저장운동·개인메모) |
| #67 | 접기/펼치기 토글 가시성 강화(테두리 바 + accent "펼치기▼/접기▲" 칩) |
| #68 | **P2** 코칭AI 설정 통합 페이지 — 카드 리스트(활성≤5 + 히스토리 5행 페이지네이션) + 코칭스타일(4프리셋+자유한줄). AI브리프지침+코치메모리 일원화, 코치챗 "내 코치 메모리" 제거, 설정 구 브리프지침 UI 제거 |
| #69 | **P3a** 코치챗 설정 CRUD: 신발·운동종목(manage_setting 도구 + 확인카드) |
| #70 | **P3b** 코치챗 개인메모 추가 + 런타입 per-user 하이브리드 마이그레이션 SQL |
| #71 | 런타입 차단 해제(마이그레이션 적용 후 코드 flip) — "내 런타입에 펀런 추가" 동작 |
| #72 | **치명적** app_settings upsert onConflict:'key' → 공용 안전 helper(전면 치환) + 코치챗 stall 해소 |

## MCP 라이브 검증 (오너계정, 콘솔에러 0)

- 코칭AI 설정 페이지: 옛 4필드 브리프지침이 **카드로 이관**(활성 1 + 히스토리 1) 정상.
- 코칭스타일 변경 저장 ✅ / 지침카드 추가 ✅ / 삭제 ✅ (이전엔 "저장 실패"·"추가 실패"였음 → 해소).
- 코치챗 "내 런타입에 펀런 추가해줘" → **정체 없이** 확인카드 → "네" → **"✅ 런타입 '펀런' 추가했어요"** (실제 생성됨, 오너계정에 '펀런' 런타입 1건 존재).

## 근본원인·교훈 (반드시 인지)

- `app_settings` 는 **per-user RLS + (user_id, key) 복합 unique**(단독 key unique drop). 그런데 코드 전반이 `.upsert({key,value},{onConflict:'key'})` 사용 → 없는 단독 제약을 가리켜 **"no unique constraint matching ON CONFLICT" 에러**.
- 기존 코드(Settings.upsertSetting 등)는 에러를 **무시**해 조용히 실패해 왔고(목표·루틴·저장운동·메모·브리프캐시 저장이 그간 안 됐을 수 있음), 신규 코치 코드는 throw 해서 드러남.
- **수정**: `src/lib/appSettings.ts` 의 `upsertAppSetting`(onConflict 미사용, 본인 행 select→update/insert, RLS user 스코프)로 전면 치환 — coachAiNotes·settingsTools·briefGenerate·Settings·History·LogEntry·SummaryBrief. 코치챗 stall 은 컨텍스트 로드 `Promise.all`→`allSettled` 로 차단.
- **교훈**: Codex 검수는 diff 로직만 본다. **코드↔라이브DB 스키마 불일치**(이번 onConflict)는 못 잡으니, app_settings 쓰기·RLS 관련은 golden_set 스키마와 대조하거나 MCP 라이브로 확인할 것.

## 핵심 기술 상태 (다음 스레드 참고)

- 신규 lib: `src/lib/coachAiNotes.ts`(통합 카드 모델·이관·주입), `src/lib/coachChat/settingsTools.ts`(manage_setting: shoe/exercise/run_type/memo), `src/lib/appSettings.ts`(안전 upsert).
- 코치챗 도구(워커 chat-proxy 배포됨): 기록(log_*)·기록수정(update_*)·테마·프로필카드·피드·대시보드공개·복수공개·브리프생성·manage_setting. systemPrompt 버전 `rc2-17-runtype`.
- 런타입: 하이브리드(글로벌 기본 user_id NULL + 사용자별). exercise_configs 와 동형. 기본제공(is_basic/user_id NULL)은 코치챗 수정·삭제 사전차단.
- 적용 완료 마이그레이션: `migrations/2026-06-09_run_type_per_user_hybrid.sql`(사장님 Supabase 적용·검증 완료).

## 다음 스레드 작업 후보 (인계)

1. **앱 전반 저장 정상화 회귀확인** — #72 로 그간 조용히 실패하던 다른 app_settings 저장(피트니스 목표 편집·저장루틴/운동·개인메모·브리프 시각화 캐시)도 복구됐다. 한 번씩 눌러 영속 확인 권장.
2. **구조 무거운 엔티티는 UI 영역(의도)** — 피트니스 목표(체성분 스펙·기간)·루틴(다중 세트)·저장운동은 한 문장 음성 CRUD 부적합 → 설정 UI 유지로 결정. 필요 시 재론.
3. **열린 제품 결정**: (a) 코치챗 **도구 턴 무차감**(원가만 로깅)이 의도한 과금모델인지 확정. (b) manage_setting 운동종목 category 무효값 → '기타' 흡수(현재 의도) vs 에러.
4. **상시 대기(누락금지)**: 약관(작업5)·디자인 이식·KB 훅 승인 / "AI에게 보내기" 문구 "30일"→실제 전체기간 / 외부 LLM KB 열람 차단 해제.
5. 코칭AI 카드 음성 활성토글·삭제(현재 추가만 "기억해줘"로 가능, 참조 특정 어려움) — 필요 시 설계.

## 지킬 운영 규칙 (요약)

- 본레포=개발만(브랜치+PR, push·머지는 사장님 명시 시 진행). git pull 금지(fetch→origin/main 분기). reset/checkout(discard)/add -A 금지·수정파일만 명시 스테이징.
- 워커 배포 함정: Bash cwd 유지 → `cd workers/chat-proxy` + pwd 확인 후 `npx wrangler deploy`.
- SQL 안내: [필수보존]/🗑️DELETE AFTER USE 태그·1copy=1run. 비밀정보 금지.
- KB 발행: dallog-tools kb-inbox frontmatter+분리블록→push. 비개발 사장님께 평어 해설·콜론종결 금지.
