---
title: "작업보고 — 코칭AI 커스텀(설정 통합·코치챗 설정 CRUD) + app_settings 치명적 저장버그 수정"
category: "workreport"
document_type: "작업보고"
source_status: "generated"
knowledge_group: "03_history"
priority: "High"
purpose: "코칭AI 커스텀(설정 통합 페이지·코치챗 설정 CRUD)과 app_settings 치명적 저장버그(onConflict:'key' 복합 unique 불일치로 앱 전반 무음 저장실패)를 전면 수정한 작업보고(PR#65~72, 핸드오프 20260609_185623의 작업보고판). 안전 upsert helper(upsertAppSetting) 전면 치환, 런타입 per-user 하이브리드 마이그레이션, Codex 검수 한계(코드↔라이브DB 스키마 불일치 미탐지) 교훈 포함."
read_when: ["코치노트AI","최신상태복구","DB·RLS·보안"]
updated: "2026-06-09"
work_timestamp: "20260609_185700"
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 치명적 저장버그 수정 (PR #65~72)

## 한 줄 요약

코치 채팅을 **"유저가 설정에서 손으로 하던 일을 말로 대신"** 하도록 확장(코칭AI 설정 통합 페이지 + 신발·운동종목·런타입·개인메모를 말로 추가/수정/삭제)했고, 그 과정에서 **앱 전반의 설정 저장(app_settings)이 원래부터 조용히 깨져 있던 치명적 버그를 발견·전면 수정**했다. 전부 라이브(오너계정 MCP)로 검증 완료.

## 배경 (비개발 사장님께 드리는 쉬운 설명)

달록 코치는 단순 상담 챗봇이 아니라 **"앱을 대신 조작해 주는 비서"** 를 지향한다. 이번 작업은 그 비서가 다룰 수 있는 "내 설정"의 범위를 넓힌 것이다. 예전엔 신발·운동종목·런타입을 추가하려면 설정 화면에 들어가 직접 입력해야 했는데, 이제는 코치에게 **"내 런타입에 펀런 추가해줘"** 라고 말하면 확인 카드를 띄우고 "네" 하면 실제로 추가된다.

그런데 이 작업 도중, **설정 저장이 앱 곳곳에서 소리 없이 실패하고 있던 숨은 폭탄**을 찾아냈다. 사용자가 "저장했다"고 생각한 목표·루틴·메모가 실제로는 DB에 안 들어가고 있을 수 있었던 문제다. 이번에 뿌리째 고쳤다.

## 완료 항목 (PR 기준, 전부 main 머지 + 자동배포)

| PR | 분류 | 내용 |
|---|---|---|
| **#65** | 비용 방어 | chat-proxy가 한 AI가 막히면 비싼 GPT로 자동 폴백하던 것을 차단(플랜 경제성). 결제사 오류는 사용자에게 기술 원문 대신 친절 안내로 노출. |
| **#66** | UX | 설정의 목록 6종(신발·런타입·운동종목·저장루틴·저장운동·개인메모)을 기본 접힘으로. 화면이 길어 핵심이 묻히던 문제 해소. |
| **#67** | UX | 접기/펼치기 토글 가시성 강화(테두리 바 + 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로 전면 치환.** 아래 별도 상술. |

## 근본 원인·교훈 — app_settings 저장 버그 (PR #72, 반드시 인지)

### 무엇이 문제였나
- `app_settings` 테이블은 **`(user_id, key)` 복합 unique**(사용자별로 같은 key 1개)로 설계돼 있다.
- 그런데 앱 코드 전반이 `.upsert({ key, value }, { onConflict: 'key' })` 처럼 **존재하지 않는 "단독 key unique"를 가리켜** 저장하고 있었다.
- Postgres는 *"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 라이브로 직접 확인할 것.

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

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

## 핵심 기술 상태 (인계)

- 신규 lib: `coachAiNotes.ts`(통합 카드 모델·이관·주입), `coachChat/settingsTools.ts`(`manage_setting`: shoe/exercise/run_type/memo), `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 저장(목표·저장루틴/운동·개인메모·브리프 시각화 캐시)도 복구됐다. 한 번씩 눌러 영속 확인 권장. *(→ 다음 스레드에서 코드 전수 확인 완료: 잔존 `onConflict:'key'` 0건. 작업보고 20260609_231135 참조.)*
2. **열린 제품 결정**: 코치챗 **도구 턴 무차감**(원가만 로깅)이 의도한 과금 모델인지 확정. *(→ 다음 스레드에서 "무차감 유지" 권고로 정리.)*

## 작업 리드타임

- 이전 스레드 작업(PR #65~72) 완료: 2026-06-09 약 18:56 (핸드오프 185623 발행 시점 기준).
- 본 작업보고 작성: 2026-06-09 약 19:00.
