PRD 신규 작성 (AI-Ready PRD) - Beupgo 유튜브용


1.요약 (Executive Summary)

1.1 앱이름

1.2 한 줄 가치 제안 (One-liner)

1.3 핵심 가치 키워드 (Core Value)

1.4 대상 사용자 (Target Users) 

1.5 타깃 플랫폼 & 기술원칙 (Target Platform & Guardrails) 

AI를 위한 코딩 수칙 요약
SwiftUI·MVVM, async/await만, NSError 금지, 모든 문자열은 Localizable.xcstrings 키로, 버튼은 지정 패턴만, 로그는 import os.log 를 사용.

1.6 MVP 스코프 (What’s In for v1.0) 

핵심 여정(한 번에 끝까지 가는 “관계 회복 세션") 
1.6.1 관계선택(ConflictSectionView)
1.6.2 1단계 - 마음정리(GuidedJournalView, 5문항) 
1.6.3 2단계 - 마음편지작성(MessageComposerView)
1.6.4 3단계 - 전달(Delivery)
1.6.5 수익화(PaywallView)
Out (MVP 범위 외)
회원 시스템/실시간 협업/웹·안드로이드 클라이언트/AI 장문 코칭(지속 대화)/크로스 디바이스 동기화.

1.7 차별점 (Why KindVerb)

1.8 비즈니스 모델 요약 (Monetization Snapshot)

1.9 성공지표 (Success Metrics) 

Metric정의목표(MVP)측정
Activation첫 실행 → 첫 편지 완성 비율
≥ 30%
이벤트 퍼널
D1 Retention다음날 재방문율
≥ 35%
리텐션 코호트
Crash-free크래시 없는 세션 비율
≥ 99.8%
Crashlytics
TTICold Launch → 첫 터치 가능
≤ 2.0s
계측 로그
Pro 전환구매 전환율(MAU 대비)
3–5%
IAP 이벤트
※ TTI(Time To Interactive) : 앱을 실행한 뒤, 사용자가 실제로 첫 터치를 할 수 있을 때까지 걸리는 시간을 의미

1.10 일정(알파 → 출시) & 마일스톤 (4주 MVP)

1.11 리스크 & 가드레일(요약)


2.문제정의&맥락 (Problem Statement & Context)

2.1 문제(Problem)

2.2 기존 대안 & 한계 (Existing Alternatives & Limitations) 

따라서 현재 시장에는 “갈등 후 즉시, 안전하고 공감적인 화해 언어를 만들어 전달” 해주는 적합한 솔루션이 부족합니다.

2.3 해결 가설 (Solution Hypothesis) 

KindVerb는 다음과 같은 방식으로 문제를 해결합니다 : 
  1. 구조화된 5단계 자기 성찰 질문(5Q Flow)
  2. AI 기반 NVC 템플릿 메시지 생성
  3. 사용자 주도 리라이팅 옵션
  4. 다양한 전달 방식 지원
  5. 프라이버시 중심 설계
👉 가설 : 사용자는 즉시 사용할 수 있는 화해 언어 도구를 원하고, 이 경험은 재방문율과 Pro 업그레이드로 이어질 것이다.

2.4 제품 경계 (Product Boundaries) 

2.5 인사이트 (Contextual Insight)

3.목표, 비목표, 성공지표 (Goals, Non-Goals, Success Metrics) 

3.1 목표(Goals, 우리가 반드시 달성해야 할 것)

3.1.1 “간단하고 직관적인 감정 기록"
3.1.2 “건강한 표현 제안" 
3.1.3 “안정성과 신뢰성 확보" 

3.2 비목표 (Non-Goals, 이번 범위에 포함하지 않는 것)

3.3 성공지표 (Success Metrics, 정량적으로 측정 가능한 목표)

Metric정의목표측정방법
D1 Retention설치 후 다음 날 앱을 다시 연 사용자 비율≥ 35%Analytics 이벤트 (install → session_start)
Crash-Free Sessions크래시 없는 세션 비율≥ 99.8%Firebase Crashlytics
TTI (Time To Interactive)앱 실행 → 첫 입력 가능까지 걸린 시간≤ 2.0s계측 로그
Emotion Log Completion Rate감정 기록 플로우 완료율 (기록 시작 → 저장 완료)≥ 80%이벤트 퍼널 추적
AI Suggestion Usage제안된 문장 복사/공유 비율≥ 30%이벤트 로그 (copy_suggestion, share_suggestion)

4.페르소나 & JTBD (Personas & Jobs To Be Done)

4.1 주요 페르소나 (Key Personas)

4.1.1 Persona1: “에밀리(28세, 직장인)”
4.1.2 Persona2 : “민수(34세, 기혼, 두 아이의 아버지)”
4.1.3 Persona3: “소라(22세, 대학생)” 

4.2 JTBD (Jobs To be Done) 

JTBD 문장은 “When ___, I want to ___, so I can ___.” 형식으로 작성.
  1. When I feel frustrated during a conversation with my partner,
    I want to record my emotions quickly in the app,
    so I can avoid saying things that might hurt them.
  2. When I argue with my kids after work,
    I want to see a suggested kind sentence template,
    so I can calm the situation without raising my voice.
  3. When I experience stress in daily life (school, work, or relationships),
    I want to log my feelings privately and safely,
    so I can reflect later and improve how I express myself to others.

5.주요 사용 사례 & 사용자 스토리 (Top Use Cases & User Stories) 

5.1 핵심 사용 사례 (Top Use Case)

앱 KindVerb의 MVP 단계에서 가장 중요한 3~4가지 시나리오를 정의합니다.
각각은 Gherkin 문법 (Given-When-Then) 으로 작성하여, AI가 바로 테스트 케이스로 변환할 수 있도록 합니다.

5.2 사용자 스토리 (User Stories)

아래 사용자 스토리들은 제품의 감동 포인트를 잘 드러내어, 단순 기능을 넘어서는 정서적 가치를 보여줍니다.
(언론 인터뷰, 투자 피치덱, 앱스토어 스토리 등에서 바로 활용 가능) 
5.2.1 Story1: “상처를 줄이지 못했던 말들이 줄어들었어요" 
민수(34세, 아버지)는 아이들에게 자주 화를 냈습니다. 그러나 KindVerb를 사용한 뒤, 그는 화가 날 때마다 감정을 기록했고 앱이 제안한 친절한 문장을 선택했습니다. “예전 같으면 큰소리를 쳤을 상황에서, 지금은 ‘아빠가 피곤해서 목소리가 커졌어, 네가 잘못한 건 아니야.’라고 말할 수 있었어요.”  그의 아이들은 점점 아빠와 대화하는 시간이 더 안전하고 따뜻하다고 느끼게 되었습니다. 
5.2.2 Story2: “사랑하는 사람을 지킬 수 있는 도구가 되었어요" 
에밀리(28세, 직장인)는 연인과의 잦은 다툼으로 힘들어했습니다. 감정이 올라올 때마다 날카로운 말을 내뱉곤 했습니다. KindVerb를 사용하면서, 감정을 기록하는 과정이 잠시 멈추는 시간을 만들어 주었고, 앱이 제안하는 부드러운 문장을 통해 연인과 다시 소통할 수 있었습니다. “싸움이 줄어들고, 연인과 함께 미래를 이야기할 수 있는 여유가 생겼어요.” 
5.2.3 Story3: “나 자신을 더 잘 알게 되었어요.” 
소라(22세, 대학생)는 SNS에 솔직한 감정을 적기 부담스러워했습니다. KindVerb에만 자신의 감정을 기록하면서, 그녀는 자신이 반복적으로 불안해하는 순간들을 발견했습니다. 앱의 리플렉션 기능을 통해, 그녀는 자신의 감정 패턴을 차분히 들여다볼 수 있었고, “예전보다 더 나를 이해하게 되었고, 인간관계에서도 덜 불안해졌어요.” 라고 말했습니다. 

5.3 감동 포인트 요약 (PR/인터뷰용)

5.4 앱스토어 설명문 (App Store Description) 

5.4.1 앱이름
5.4.2 한  줄 소개 (Subtitle)
5.4.3 주요소개 (Long Description) 
KindVerb는 감정을 건강하게 기록하고, AI가 제안하는 친절한 문장으로 따뜻한 대화를 이어갈 수 있게 돕는 iOS 앱입니다.
5.4.4 USP (Unique Selling Point) 
5.4.5 앱스토어 키워드 예시
감정 기록, 대화, 감정 관리, 커뮤니케이션, 연애, 자기계발, 멘탈헬스
5.4.6 앱스토어 미리보기 스크린샷 카피
  1. “단 3초, 감정을 기록하세요.”
  2. “AI가 제안하는 친절한 문장으로 대화를 이어가세요.”
  3. “지난 대화를 돌아보며 성장하세요.”
  4. “오프라인에서도 감정을 놓치지 마세요.”

5.5 마케팅 에셋 (AppStore, SNS, Landing Page용 카피)

5.5.1 앱스토어용 스토리 (스토어 피쳐 추천용)
“KindVerb는 단순한 감정 기록 앱이 아닙니다. 이 앱은 우리가 사랑하는 사람과 더 따뜻하게 연결될 수 있도록 돕는 작은 도구입니다. AI가 제안하는 ‘친절한 한마디'는, 당신의 하루와 관계를 바꿀 수 있습니다.” 
5.5.2 SNS 홍보용 짧은 카피 (트위터/인스타/스레드) 
5.5.3 랜딩페이지 섹션 카피
5.5.4 PR/인터뷰용 키 메시지

6.범위&우선순위 (Scope & Prioritization - Gherkin)

6.1 기능 목록 요약 (MoSCoW)

ID 기능명 요약 사용자 가치 우선순위
F-01 관계 유형 선택 5개 관계 카드(Free 2, Pro 3)에서 여정 시작 적합한 여정으로 빠른 진입 Must
F-02 1단계: 마음 정리(5문항) 상황·감정·상처·반성(선택)·진짜 원했던 것 입력 감정의 언어화·흥분 완화 Must
F-03 2단계: AI ‘마음 편지’ 작업실 NVC 기반 초안 생성/수정(부드럽게/간결하게/다시 제안/진솔하게) 상처 줄이는 대화문 자동 생성 Must
F-04 3단계: 전달(복사/이미지/링크) 편지 복사, 편지지 이미지 저장, 7일 만료 링크 공유 즉시 실사용 가능 Must
F-05 인앱 결제(Pro 평생권) Pro 카테고리 해제, 구매/복원, 실패 처리 수익화·지속가능성 Must
F-06 로컬 저장/오프라인 큐/재시도 SwiftData 저장, 오프라인 큐잉, 온라인 시 자동 재시도 네트워크 무관 안정성 Should
F-07 리플렉션(최근 기록 요약) 최근 30일 감정 태그 빈도/간단 지표 카드 스스로의 변화 가시화 Should
F-08 알림(부드러운 리마인더) 감사 노트/감정 기록 친절 알림(주 2회 이하, 사용자가 온/오프) 습관화·리텐션 Could
F-09 라이브러리(전문가 칼럼 읽기) 5~8개 핵심 글의 온디바이스 번들(외부 링크 無) 교육 효과·브랜드 신뢰 Could
F-10 계정/로그인/클라우드 동기화 사용자 계정/원격 백업 개인정보·개발 복잡도 증가로 이번 범위 제외 Won’t
F-11 광고/추적 SDK 서드파티 광고/트래킹 제품 신뢰/집중도 저해, 이번 범위 제외 Won’t

6.2 RICE 우선순위 (MVP 기준 가정치) 

ID R I C E(weeks) RICE 코멘트
F-05 6k 3 0.8 1.5 9,600 수익 임팩트 최상, 구현 난이도 중
F-03 6k 3 0.7 2.0 6,300 코어 가치, 모델 품질이 핵심
F-04 6k 2 0.9 1.0 10,800 공유/바이럴 직접 기여
F-02 6k 2 0.9 1.0 10,800 핵심 입력 파이프
F-01 6k 2 0.95 0.5 22,800 첫 진입 허들 제거
F-06 6k 2 0.8 1.0 9,600 신뢰/안정성
F-07 4k 2 0.7 1.0 5,600 리텐션 보조
F-08 4k 1 0.8 0.5 6,400 가벼운 노력 대비 효과 괜찮음
F-09 3k 1 0.7 0.8 2,625 신뢰 형성용
결론(개발 순서 추천): F-01 → F-02 → F-03 → F-04 → F-05 → F-06 → (F-07/F-08) → F-09

6.3 기능별 스펙 (Gherkin + 수용 기준) 

6.3.1 F-01 유형 선택 (Conflict Selection) 
Feature: Conflict type selection
As a user I want to start with a relationship context to receive tailored guidance.

Scenario: Show 5 conflict cards with Free/Pro badges
Given app launched first time
When the user reaches the conflict selection screen
Then 5 cards are visible
And "friend" and "partner" cards show a "Free" badge via key "conflict.badge.free"
And "parent_child", "siblings", "in_laws" cards show a "Pro" badge via key "conflict.badge.pro"

Scenario: Start Free journey
Given the user is on the conflict selection screen
When the user taps the "Start" button on "friend"
Then navigate to Guided Journal with conflictType="friend"

Scenario: Tap Pro card leads to paywall
Given the user is on the conflict selection screen
When the user taps the "Start Pro" button on "parent_child"
Then show Paywall screen

Scenario: Accessibility and localization keys are used
Given voiceover is on
Then card titles/CTA use localization keys only
6.3.2 F-02 1단계: 마음정리(5문항)
Feature: Guided journal (5 steps)
Scenario: Complete all steps and proceed
Given the user selected conflictType="friend"
When the user enters valid inputs for steps 1..5
Then tap "journal.complete" button
And navigate to JournalCompletion

Scenario Outline: Validation and limits
Given the user is on step <step>
When the user enters input exceeding the limit or invalid
Then show error with key <errorKey>
Examples:
| step | errorKey |
| 1 | error.journal.too_long |
| 2 | error.journal.emotion_empty |
| 5 | error.journal.want_empty |

Scenario: Skip reflection (step 4)
Given the user is on step 4
When the user taps "journal.skip.reflection"
Then proceed to step 5

Scenario: Offline saving
Given the device is offline
When the user completes step 5
Then data is saved locally (SwiftData)
And queued for sync when online (event "sync.queue_enqueued")
6.3.3 F-03 2단계: AI ‘마음 편지' 작업실
Feature: AI message composer
Scenario: Draft is generated on entry
Given the user completed Guided Journal
When opening MessageComposer
Then show loading indicator
And populate editor with AI draft within 5 seconds or show "composer.retry" action

Scenario: Assistant buttons transform text
Given the editor has a draft
When the user taps "composer.assist.soften"
Then the text updates with a softer tone
And event "ai.rewrite" is logged with variant="soften"

Scenario: Failure and recovery
Given the AI service is unreachable
When draft generation fails
Then show error key "error.ai.unavailable"
And expose "composer.retry" action
6.3.4 F-04 3단계: 전달(복사/이미지/링크) 
Feature: Delivery options
Scenario: Copy to clipboard
Given a finalized letter in editor
When the user taps "delivery.copy"
Then letter text is in clipboard
And toast with key "delivery.copied" is shown

Scenario: Save as image
Given photo permission is granted or requested
When the user taps "delivery.image"
Then save a high-resolution image to Photos
And show "delivery.image_saved"

Scenario: Share via link (online)
Given the device is online
When the user taps "delivery.link"
Then create Firestore document with { content, createdAt }
And return URL "https://kindverb.com/letter/<id>"
And show iOS share sheet

Scenario: Share via link (offline)
Given the device is offline
When the user taps "delivery.link"
Then show error "error.network.offline"
6.3.5 F-05 인앱 결제 (Pro 평생권) 
Feature: In-App Purchase (Pro Lifetime)
Scenario: Purchase success unlocks Pro features
Given the user is on Paywall
When the user completes purchase successfully
Then unlock all Pro categories immediately
And replace Pro badges with unlocked state
And log "iap.purchase_succeeded"

Scenario: Restore purchases
Given the user taps "iap.restore"
When a valid receipt is found
Then set Pro state to true
And show "iap.restore_success"

Scenario: Purchase failure
When the purchase fails or is canceled
Then show error key "error.iap.failed" or "error.iap.canceled"
6.3.6 F-06 로컬 저장・오프라인 큐・자동 재시도
Feature: Offline-first storage and retry
Scenario: Save journal offline
Given no connectivity
When the user completes step 5
Then journal is persisted locally (SwiftData)
And a sync job is enqueued

Scenario: Auto-retry on reconnect
Given queued jobs exist
When connectivity is restored
Then jobs are sent
And success clears the queue

6.4 에지 케이스 (공통)

6.5 텔레메트리 이벤트(요약)

이벤트 ID 설명 주요 속성(Properties) 샘플 값 예시 목적
ai.draft_generated AI가 초안을 생성했을 때 latency_ms, tokens_in, tokens_out, model, conflict_type 1240, 612, 284, "gpt-5", "friend" 모델 성능 및 사용 맥락 측정
ai.rewrite 사용자가 리라이팅 옵션 선택 variant(soften/concise/honest/retry), conflict_type, draft_length, latency_ms, reason(optional) "soften", "partner", 842, 980 리라이팅 패턴/선호도 분석
ai.accepted 사용자가 AI 문안을 최종 채택 draft_id, time_to_accept_ms "d123", 4520 채택률, 사용자 신뢰도 측정
ai.discarded 생성된 문안을 버림 draft_id, reason "d124", "too_generic" 버려지는 이유 분석 및 품질 개선
user.note_saved 감정 기록(노트) 저장됨 emotion, note_length, was_offline "angry", 240, false 핵심 기능 사용률 추적
user.session_start 앱 세션 시작 timestamp, entry_point 2025-08-21T07:23:12Z, "push_notification" 세션 빈도 및 유입 경로 파악
user.session_end 앱 세션 종료 duration_ms, crash_free 380000, true 세션 길이, 안정성 측정
delivery.link_shared AI 문안을 링크로 공유 url_prefix, ttl_days, content_length, was_online "https://kindverb.com/letter/", 7, 1260, true 공유 기능 효과 측정
iap.purchase_succeeded 결제 성공 sku, price, currency, platform "kindverb.pro.monthly", 5900, "KRW", "ios" 매출 분석
iap.purchase_failed 결제 실패 sku, error_code, platform "kindverb.pro.monthly", "storekit_timeout", "ios" 결제 실패율, 개선 포인트
setting.locale_changed 사용자가 언어 설정 변경 old_locale, new_locale "en", "ko" 다국어 사용 패턴 추적

6.6 AI-Ready 작성 가드레일


7.앱플로우&정보구조 (App Flow & Information Architecture, IA) 

7.1 네비게이션 모델 (MVP)

V01_LaunchScreen
↓ auto
V02_ConflictSelectionView
├─[Free 선택]→ V04_GuidedJournalView
└─[Pro 선택] → V03_PaywallView (sheet)
├─[구매 성공]→ V04_GuidedJournalView
└─[취소/닫기]→ V02
V04_GuidedJournalView (5-step)
↓ [정리 완료]
V05_JournalCompletionView
↓ [2단계로]
V06_MessageComposerView
├─[텍스트 복사]
├─[이미지 저장]→ (포토 권한 요청·성공 시 저장)
└─[링크 공유]→ (온라인만 활성/성공 시 URL 발급)

V07_CompletionView → V02 로 귀환

7.2 화면 인벤토리 (Screen Inventory)

ID 화면명 목적/설명 진입 주요 액션 → 다음 데이터 입/출력 계약 표시 텍스트 키(예) 텔레메트리
V01 LaunchScreen 브랜딩·첫 인상, 1~1.5s 후 자동 전환 앱 실행 자동 → V02 입력: 없음 / 출력: 없음 launch.tagline, launch.accessibility_label user.session_start
V02 ConflictSelectionView 관계 유형 선택(Free/Pro 배지) V01 [Free 시작] → V04, [Pro로 시작] → V03(sheet) 입력: 없음 / 출력: conflictType conflict.title, conflict.cta.free, conflict.cta.pro ui.conflict_shown, ui.conflict_selected
V03 PaywallView (Sheet) Pro 가치 제시·구매/복원 V02 [구매]→영수증 검증 성공→V04, [복원], [닫기] 입력: selectedConflictType / 출력: entitlement=pro paywall.title, paywall.benefits.*, paywall.buy, paywall.restore iap.paywall_shown, iap.purchase_succeeded/failed
V04 GuidedJournalView 5개 질문으로 성찰(1단계) V02 또는 V03 [다음] 순회, [정리 완료]→V05 입력: conflictType / 출력: journalDraft (로컬 저장) journal.q1.*~journal.q5.*, journal.next, journal.complete journal.step_shown, journal.completed
V05 JournalCompletionView 1단계 완료 피드백 V04 [2단계로]→V06 입력: journalDraft / 출력: 없음 journal.done.title, journal.done.cta journal.completion_shown
V06 MessageComposerView AI 초안/수정, 최종 본문 확정 V05 [다시 제안/부드럽게/간결/진솔], [텍스트 복사], [이미지 저장], [링크 공유]→V07 입력: journalDraft / 출력: messageDraft, sharedLink? composer.title, composer.tools.*, composer.share.* ai.draft_generated, ai.rewrite, delivery.link_shared, delivery.image_saved, delivery.text_copied
V07 CompletionView 여정 마무리·격려 V06 [확인]→V02 입력: deliveryMethod / 출력: 없음 completion.title, completion.tip, completion.ok user.session_end (또는 홈 회귀 이벤트)
주의: 모든 화면의 사용자 노출 텍스트는 직접 문자열 금지, 반드시 현지화 키 사용. 버튼은 Button { } label: { } 형식만.

7.3 상태 다이어그램 (요약) 

7.3.1 앱 전역 상태 (오프라인/권한/Pro) 
[앱 시작]
├─ Network: Online / Offline
│ ├─ Online: 링크 공유 가능
│ └─ Offline: 링크 공유 비활성 (토스트: error.network.offline)
├─ Entitlement: Free / Pro (paywall 성공·복원 시 Pro)
└─ Photo Permission: NotDetermined → Denied/Authorized
├─ 이미지 저장 시 요청
└─ Denied면 설정 이동 안내 키 노출
7.3.2 1단계 성찰(5-Step) 상태
Q1 → Q2 → Q3 → Q4(건너뛰기 허용) → Q5 → [정리 완료] → 완료화면
(각 스텝은 ViewModel 임시 상태에 저장, 완료 시 SwiftData 영구 저장)
7.3.3 Pro 결제 상태
Free → (구매 성공/복원) → Pro
실패/취소 → Free 유지 (에러 키/가이드 표시)

7.4 권한 플로우 (Permissions)

사용 시점 권한 트리거 화면 UX 요구사항(키) 실패/거부 시
이미지 저장 버튼 탭 Photos AddOnly V06 perm.photos.rationale, perm.photos.go_settings 버튼 비활성 또는 설정 이동 안내
(선택) 알림 리마인더 Notifications (후속 버전) perm.push.rationale 요청 스킵 가능(기본 거부 가정)
MVP에서는 포토 권한만 필수. 알림은 후속 버전 고려.

7.5 딥링크 & 라우팅 (Deeplink / Routing) 

유형 패턴 동작
앱 내부 라우트 kindverb://open/conflict/{type} V02를 열고 해당 카드 하이라이트
(선택) 웹→앱 유니버설링크 https://kindverb.com/letter/{id} 앱 설치 시 뷰어 장착 가능(초기 버전은 웹 전용 페이지 노출을 기본)
인앱 네비 NavigationPath로 V02→V04→V05→V06→V07 뒤로가기 제스처 허용(데이터 보존)
MVP에서는 링크 뷰어 웹 우선. 나중에 Universal Links를 앱에 매핑해 읽기 전용 뷰 추가 가능.

7.6 정보구조 (Information Architecture)

7.6.1 도메인 엔티티 & 소유권

7.6.2 레이어별 책임 (MVVM 가드레일)

7.7 화면별 데이터 계약(요약)

화면 입력 출력 비고
V02 - conflictType Free/Pro 배지에 따라 흐름 분기
V03 selectedConflictType entitlement=pro(성공시) 실패 시 에러 키·재시도 제공
V04 conflictType journalDraft (SwiftData 저장) 각 스텝별 입력 제한(문자 수 등)
V06 journalDraft messageDraft, sharedLink? AI 요청 파라미터: conflictType, 핵심 감정/욕구 요약
V07 deliveryMethod - 홈으로 리다이렉션

7.8 로딩/빈 상태/에러 패턴

상황 패턴 사용자 메시지(키) 버튼/행동
AI 초안 생성 중 스켈레톤 또는 인디케이터 state.loading.generating_draft 취소 버튼 없음(짧은 대기 가정)
Journal 빈 입력 친절한 안내 + 예시 journal.empty.hint, journal.example.* [다음] 비활성(검증 실패 키)
오프라인 링크 공유 비활성 + 토스트 error.network.offline [다시 시도] 키
포토 권한 거부 시트 안내 perm.photos.denied [설정으로 이동]
IAP 실패 명확한 사유/재시도 `iap.error.timeout canceled
모든 에러는 NSError 금지, 현대적 Error → ViewModel에서 로컬라이즈드 키로 매핑.

7.9 디자인 토큰 적용 지점 (요약)

7.10 접근성(A11y) & 키보드 UX

7.11 로컬라이제이션 키 레지스트리(발췌)

예시 키만. 실제 문자열은 Localizable.xcstrings에서 관리.

7.12 텔레메트리(화면 매핑)

7.13 엣지 케이스 정의

7.14 IA 요약 도식
[ConflictType]───┐
├──> [JournalDraft] ──> [MessageDraft] ──> [Delivery (copy|image|link)]
[Entitlement] ──┘ │
└──(SwiftData 저장)
Services: AI(Large Language Model), IAP(StoreKit), LinkShare(Firestore), Photos

8.UI/UX 요구사항 (UI/UX Requirements) 

8.0 제품 UX 원칙 (Design Principles)

  1. 평온함(Composed): 시각적 소음 최소화, 글 읽기·쓰기 집중 지원
  2. 단순함(Focused): 한 화면 = 한 과업. 기본 선택지를 명확히 안내
  3. 친절함(Kind): 실패·거절에도 사용자를 비난하지 않음(격려형 카피)
  4. 안심(Private): 온디바이스 저장·오프라인 우선 UI로 신뢰 제공
  5. 예측 가능성(Consistent): 동일한 액션은 동일한 위치·모양·문구

8.1 디자인 토큰 (Design Tokens)

코드에서는 토큰 이름만 참조합니다. 실제 값은 테마 파일에서 관리.

8.1.1 컬러

Token 값(예시) 용도
color.bg #FAFAFA 기본 배경
color.surface #FFFFFF 카드/시트 배경
color.primary #4F46E5 주요 액션/포커스
color.text.primary #111827 본문 텍스트
color.text.muted #6B7280 보조 텍스트
color.border #E5E7EB 구분선/입력창 테두리
color.success #10B981 성공·완료 피드백
color.warning #F59E0B 주의
color.error #EF4444 오류 강조
8.1.2 간격/라운드/그림자
Token 값(예시) 설명
spacing.xs 4 최소 간격(4pt 그리드)
spacing.s 8
spacing.m 12 기본 텍스트 주변
spacing.l 16 카드 내부 패딩
spacing.xl 24 섹션 간 간격
radius.s 8 입력/태그
radius.m 12 버튼
radius.l 16 카드/시트
shadow.card iOS default 카드 미묘한 그림자
8.1.3 타이포 스케일 (Dynamic Type 대응) 
Role 폰트(예시) 크기(기본) 사용처
type.title SF Pro / Serif Body 22–28pt 화면 타이틀
type.heading SF Pro 18–20pt 섹션 제목
type.body SF Pro 15–17pt 본문(가독성 최우선)
type.caption SF Pro 12–13pt 도움말/설명
규칙: Dynamic Type 100% 지원. 대비 WCAG AA 이상 유지.

8.2 핵심 컴포넌트 (Components)

버튼 형식 강제: 모든 SwiftUI 버튼은 Button { /* action */ } label: { /* view */ } 형식만 사용.
문구는 반드시 현지화 키로 표기(예: Text("composer.share.link")).

8.2.1 PrimaryButton

8.2.2 SecondaryButton (Outline)

8.2.3 Tag/EmotionChip

8.2.4 Card

8.2.5 InlineBanner

8.2.6 TextArea (Journal Input)

8.2.7 Loading Indicator / Skeleton

8.3 UI 패턴 (Patterns)

8.3.1 빈 상태 (Empty States)

8.3.2 로딩 (Loading)

8.3.3 오류 (Errors) — 현대 Error → 키 매핑

8.3.4 오프라인 UX

8.3.5 권한 UX (Just-in-time)

8.3.6 진행(Progress) & 단계 표시

8.3.7 제스처 & 뒤로가기

8.4 특수 화면 UX 스펙

8.4.1 ConflictSelectionView

8.4.2 PaywallView (Sheet)

8.4.3 GuidedJournalView (5 스텝)

8.4.4 MessageComposerView

8.4.5 CompletionView

8.5 모션 & 햅틱 (Motion & Haptics)

8.6 접근성 (A11y)

8.7 현지화 규칙 (Localization)

샘플 키 레지스트리(발췌)

8.8 이미지 내보내기(“이미지로 저장”) 스펙

8.9 링크 공유 UX (온라인)

8.10 분석 이벤트(UX 관점 요약)

이벤트 시점 속성(예시)
ui.conflict_shown V02 노출
ui.conflict_selected 카드 CTA 탭 conflict_type
journal.step_shown Q1~Q5 노출 step_index
journal.completed [정리 완료] elapsed_ms, text_length
ai.draft_generated 첫 초안 수신 latency_ms, tokens?
ai.rewrite 도우미 4종 사용 variant
delivery.text_copied 복사 length
delivery.image_saved 이미지 저장 duration_ms
delivery.link_shared 링크 공유 완료 id_hash?, channel?
iap.paywall_shown Paywall 노출 source
iap.purchase_succeeded/failed 구매 결과 reason?

8.11 수용 기준 체크리스트 (UI/UX)


9.플랫폼 & 기술결정 + 에러 도메인 (Platform & Tech Decisions + Error Domain)

(iOS · SwiftUI · MVVM · async/await · 현대 Error · 현지화 · 오프라인 우선)

9.1 지원 환경 & 빌드 정책

9.2 계층 구조(레이어) & 책임

원칙: View는 표시와 입력만, ViewModel은 상태·의도 관리, UseCase는 업무 규칙, Repository는 도메인 I/O, Service는 외부/플랫폼 호출.
View (SwiftUI)
↕ @Published / intents
ViewModel (ObservableObject)
↕ UseCase (async)
Repository (protocols → impl: Local/Remote)

Services (SwiftData, File, Network(Firestore), IAP, ShareLink)
의존성 주입(DI): Environment/Factory로 주입. 단위 테스트에서 Fake 교체 용이.

9.3 동시성 & 성능 가드레일 (async/await)

9.4 저장소 & 네트워크 결정

9.5 인앱결제(StoreKit 2)

9.6 로깅 & 텔레메트리

9.7 피처 플래그 & 구성

9.8 테스트 전략

9.9 에러 도메인(분류 체계) — 사용자 메시지는 전부 “키”로

개발자는 타입 안전 Error로 원인·복구전략 결정 → ViewModel이 UI 상태로 매핑 → View는 만 표시.

9.9.1 에러 최상위 분류

9.9.2 서브 도메인 예시

9.9.3 에러 → UI 매핑(현지화 키)

Error 도메인사용자 메시지 키(타이틀/본문/CTA 예시)기본 복구 전략
NetworkError.offlineerror.network.offline.title, error.network.offline.body, banner.cta.retry재시도/오프라인 모드 안내
NetworkError.timeouterror.network.timeout.title, error.network.timeout.body, banner.cta.retry지수 백오프 재시도
IAPError.cancelediap.error.canceled무시(토스트), 흐름 유지
IAPError.verificationFailediap.error.verify_failed, iap.cta.retry복원 시도/재결제
PermissionError.photosDeniedperm.photos.denied.title/body, perm.photos.go_settings설정 이동
AIError.rateLimitedai.error.ratelimit, banner.cta.retry_later지연 후 재시도
LinkError.createFailederror.link.create_failed, banner.cta.retry재시도/오프라인 저장 대안 제시
ValidationError.tooLong(.journal,500)journal.error.too_long입력 길이 조정 가이드
직접 문자열 금지: 모든 사용자 문구는 로만 표기.
개발자용 상세Logger에 남김(PII 금지).

9.10 실패-복구(UX) 표준 플로우

  1. 탐지: catch AppError
  2. 분류: 도메인별로 ErrorViewState 변환(배너/토스트/시트)
  3. 복구 옵션: 재시도, 설정 이동, 입력 수정, 오프라인 대체
  4. 기록: 텔레메트리 이벤트(error.domain, error.code, screen)
  5. 사용자 보장: “진행 중 입력/초안은 소실되지 않음” 원칙 준수

9.11 코드 스니펫(아주 작은 예시, 규칙 준수)

실제 구현은 다음 섹션에서 파일 단위로 제공합니다. 여기서는 원칙 준수 예시만 간단히 남깁니다. (문자열은 , 버튼 형식 고정, async/await, 현대 Error)
// MARK: - Error 정의 (간단 예시)
// 한 줄 한 줄 주석: 왜 이렇게 구성하는지 설명합니다.

// App 전역에서 사용할 에러 최상위 타입을 정의합니다.
// 이유: 도메인별 세부 에러를 하나의 공통 타입으로 모아
// ViewModel에서 일관된 방식으로 UI 상태를 만들기 위함입니다.
enum AppError: Error {
// 네트워크 관련 에러(오프라인, 타임아웃 등)를 감쌉니다.
case network(NetworkError)
// 로컬 저장(SwiftData) 관련 에러를 감쌉니다.
case storage(StorageError)
// 인앱결제 관련 에러를 감쌉니다.
case iap(IAPError)
// 권한 거부 등 권한 관련 에러를 감쌉니다.
case permission(PermissionError)
// AI 초안 생성 실패 등 AI 관련 에러를 감쌉니다.
case ai(AIError)
// 링크 공유(Firestore) 관련 에러를 감쌉니다.
case link(LinkError)
// 입력값 검증 실패를 감쌉니다.
case validation(ValidationError)
// 분류되지 않은 모든 에러를 안전하게 수용합니다.
case unknown(underlying: Error?)
}

// 네트워크 하위 에러 예시입니다.
// 이유: 사용자 메시지는 동일하지만, 로깅과 정책은 코드별로 달라질 수 있기 때문입니다.
enum NetworkError: Error { case offline, timeout, unreachable, http(code: Int) }
// MARK: - 에러 → 현지화 키 매핑(뷰모델 내부 유틸 예시)
// 이 함수는 AppError를 받아서 화면에 표시할 "키"를 결정합니다.
// 이유: 사용자 문구는 코드에 직접 쓰지 않고, 항상 키를 반환하기 위함입니다.
func localizedKeys(for error: AppError) -> (titleKey: String, bodyKey: String?, ctaKey: String?) {
switch error {
case .network(.offline):
return ("error.network.offline.title", "error.network.offline.body", "banner.cta.retry")
case .network(.timeout):
return ("error.network.timeout.title", "error.network.timeout.body", "banner.cta.retry")
case .iap(.canceled):
return ("iap.error.canceled", nil, nil)
case .permission(.photosDenied):
return ("perm.photos.denied.title", "perm.photos.denied.body", "perm.photos.go_settings")
case .link(.createFailed):
return ("error.link.create_failed", nil, "banner.cta.retry")
default:
return ("error.unknown", nil, nil)
}
}
// MARK: - 버튼 형식·키 사용 예시(규칙 준수)
// 규칙 1: 버튼은 반드시 action/label 분리 형식을 사용합니다.
// 규칙 2: 사용자 노출 문자열은 키만 사용합니다.
Button {
// 액션: 재시도 같은 복구 로직을 실행합니다(비동기).
Task { await viewModel.retry() }
} label: {
// 라벨: 현지화 키를 Text에 넣습니다(직접 문자열 금지).
Text("banner.cta.retry")
}
.accessibilityLabel(Text("a11y.banner.retry")) // 접근성 라벨도 키 사용

9.12 보안·개인정보(플랫폼 차원 결정 요약)

9.13 유지보수·확장성 고려

9.14 이 섹션의 “확정/보류” 목록


10. 데이터모델 (Entities · JSON · Relationships/States)

10.1 모델링 원칙 (Design Principles)

10.2 엔티티 개요(요약 표)

Entity Purpose 저장소 주요 키 필드 관계
Journal 5단계 자기 성찰 세션 SwiftData id, conflictType, status, createdAt 1 — may have manyLetterDraft
LetterDraft AI/사용자 편지 초안 SwiftData id, journalId, status, content N — 1 → Journal / 1 — 0..1 → ShareRecord
ShareRecord 링크 공유 이력(로컬 트래킹) SwiftData id, letterId, remoteId?, status 1 — 1 ← LetterDraft
PurchaseState Pro 구매 상태 SwiftData isPro, purchaseDate?
Settings 앱 설정(언어/애니메이션 등) SwiftData id, 각종 토글
ConflictType 관계 유형(열거형) 코드/Key friends, couple, parentChild, siblings, inLaws Journal.conflictType
(Remote) letters 공유용 원격 문서 Firestore id, content, createdAt ShareRecord.remoteId

10.3 상세 스펙 — 엔티티별 정의

A) ConflictType (열거형 · 코드 상수)

B) Journal (자기 성찰 세션)

목적: 5단계 질문의 사용자 입력을 저장하고, 편지 생성의 근거 데이터가 됨.
필드(표)
필드 타입 필수 기본값 제약/검증 인덱스
idUUID✔︎uuid4PKPK
schemaVersionInt✔︎1마이그레이션용✔︎
conflictTypeString✔︎ConflictType 값만 허용✔︎
statusString✔︎draftdraftcompleted✔︎
situationTextString✔︎길이 ≤ 500
feelings[String]✔︎[]각 항목 길이 ≤ 24, 최대 8개
hurtTextString✔︎길이 ≤ 500
reflectionTextString?nil길이 ≤ 500
desiredNeedTextString✔︎길이 ≤ 500
createdAtISO8601✔︎now과거/미래 허용(테스트 편의)✔︎
updatedAtISO8601✔︎now저장 시 갱신✔︎

상태(STATE) & 전이(TRANSITION)
JSON 스키마(간결형)
{
"Journal": {
"id": "uuid",
"schemaVersion": 1,
"conflictType": "friends | couple | parentChild | siblings | inLaws",
"status": "draft | completed",
"situationText": "string(max:500)",
"feelings": ["string(max:24)"],
"hurtText": "string(max:500)",
"reflectionText": "string(max:500)|null",
"desiredNeedText": "string(max:500)",
"createdAt": "2025-08-21T03:14:15Z",
"updatedAt": "2025-08-21T03:14:15Z"
}
}
예시(JSON)
{
"id": "b8c7d5d7-4a8b-4f0f-9a64-6d78f1b8c1ab",
"schemaVersion": 1,
"conflictType": "couple",
"status": "completed",
"situationText": "주말 약속을 깜빡해서 다투었다.",
"feelings": ["guilty", "anxious"],
"hurtText": "내가 지키지 못한 약속 때문에 상대가 존중받지 못했다고 느꼈을 수 있다.",
"reflectionText": "변명보다 책임을 먼저 인정해야겠다.",
"desiredNeedText": "서로의 약속을 더 확실히 확인하고 싶은 마음.",
"createdAt": "2025-08-20T12:00:00Z",
"updatedAt": "2025-08-20T12:30:00Z"
}

C) LetterDraft (편지 초안: AI + 사용자 편집)

목적: 하나의 Journal로부터 여러 버전의 초안을 만들 수 있으나, 최종 1개를 선택해 공유 가능.
필드(표)
필드 타입 필수 기본값 제약/검증 인덱스
id UUID ✔︎ uuid4 PK PK
journalId UUID ✔︎ FK(Journal.id) ✔︎
schemaVersion Int ✔︎ 1 ✔︎
status String ✔︎ drafted draftededitedfinalized ✔︎
content String ✔︎ 길이 10000 이하 Full-text
aiModel String ✔︎ "gpt-5" 로깅용(문자열)
promptVersion Int ✔︎ 1 프롬프트 변경 추적
tone String ✔︎ "neutral_warm" 사전 정의 값
iteration Int ✔︎ 1 1부터 증가
createdAt ISO8601 ✔︎ now ✔︎
updatedAt ISO8601 ✔︎ now ✔︎

상태
JSON 스키마(간결형)
{
"LetterDraft": {
"id": "uuid",
"journalId": "uuid",
"schemaVersion": 1,
"status": "drafted | edited | finalized",
"content": "string(max:10000)",
"aiModel": "gpt-5",
"promptVersion": 1,
"tone": "neutral_warm | softer | concise | sincere",
"iteration": 1,
"createdAt": "2025-08-21T03:14:15Z",
"updatedAt": "2025-08-21T03:14:15Z"
}
}

D) ShareRecord (링크 공유 로컬 추적)

목적: 원격 공유 결과/만료를 로컬에서 추적하고, 재시도/실패 UI를 제어.
필드(표)
필드 타입 필수 기본값 제약/검증 인덱스
id UUID ✔︎ uuid4 PK PK
letterId UUID ✔︎ FK(LetterDraft.id), 유일(편지당 1개) ✔︎(unique)
remoteId String? nil Firestore 문서 ID ✔︎
url String? nil https://kindverb.com/letter/{remoteId}
status String ✔︎ pending `pending success
contentHash String ✔︎ sha256 길이=64(hex)
expiresAt ISO8601? nil Firestore TTL 기준 복제
createdAt ISO8601 ✔︎ now ✔︎
updatedAt ISO8601 ✔︎ now ✔︎
상태
원격(파이어스토어) letters 컬렉션

E) PurchaseState (Pro 구매 상태)

필드타입필수기본값제약
idUUID✔︎uuid4PK
isProBool✔︎false
purchaseDateISO8601?nilPro일 때만 존재
originalTransactionIdString?nil검증 추적
lastVerifiedAtISO8601?nil영수증 검증 타임스탬프

F) Settings (앱 설정)

필드타입기본값설명
idUUIDuuid4PK
appLanguageString"system"`"en"
enableTypingAnimationBooltrue편지 타이핑 애니메이션
reducedMotionBoolOS설정따름접근성 반영 캐시

10.4 관계(ER 다이어그램 텍스트)

삭제 전파 규칙

10.5 검증 규칙 (Validation)

10.6 인덱싱 & 조회 패턴 (SwiftData)

10.7 상태 머신 정리(표)

Journal

FromTo조건
draftcompleted모든 필수 필드 유효(검증 통과)

LetterDraft

FromTo조건
draftededited사용자가 내용 수정
editedfinalized최종 검토 완료(길이·금칙어·Pro게이트)

ShareRecord

FromTo조건
pendingsuccessFirestore create 성공, remoteId 수신
pendingfailed네트워크/규칙 에러
pendingcanceled사용자가 공유 취소

10.8 오류 메시지 키(데이터 계층 관련)

UI 문구는 만 사용(실 문자열 금지). 아래 키는 9장에서 정의한 에러 도메인과 합치.
상황키 예시
길이 초과journal.error.too_long
필수 누락journal.error.required_missing
전이 불가journal.error.invalid_state_transition
외부 공유 실패error.link.create_failed

10.9 샘플 픽스처 (End-to-End)

완료된 세션 → 초안 2개 중 1개 확정 → 공유 성공 흐름
{
"Journal": {
"id": "3c1f7a15-4b1c-4e94-a05f-9f2a4a6b0c11",
"schemaVersion": 1,
"conflictType": "parentChild",
"status": "completed",
"situationText": "숙제 문제로 언성을 높였다.",
"feelings": ["worried", "frustrated"],
"hurtText": "아이의 자율성을 침해했다고 느꼈을 수 있다.",
"reflectionText": "지적보다 공감을 먼저 하자.",
"desiredNeedText": "서로 일정을 합의하고 존중받고 싶은 마음.",
"createdAt": "2025-08-19T10:00:00Z",
"updatedAt": "2025-08-19T10:15:00Z"
},
"LetterDrafts": [
{
"id": "7e6f8d9a-3d1e-44a7-9b02-8e0f2e9d1234",
"journalId": "3c1f7a15-4b1c-4e94-a05f-9f2a4a6b0c11",
"schemaVersion": 1,
"status": "edited",
"content": "어제 내 말투가 상처가 되었을 것 같아...",
"aiModel": "gpt-5",
"promptVersion": 1,
"tone": "softer",
"iteration": 2,
"createdAt": "2025-08-19T10:16:00Z",
"updatedAt": "2025-08-19T10:20:00Z"
},
{
"id": "1a2b3c4d-55aa-66bb-77cc-8899ddeeff00",
"journalId": "3c1f7a15-4b1c-4e94-a05f-9f2a4a6b0c11",
"schemaVersion": 1,
"status": "finalized",
"content": "어제 네 마음을 충분히 듣지 못한 점 미안해...",
"aiModel": "gpt-5",
"promptVersion": 1,
"tone": "sincere",
"iteration": 3,
"createdAt": "2025-08-19T10:21:00Z",
"updatedAt": "2025-08-19T10:25:00Z"
}
],
"ShareRecord": {
"id": "0f9e8d7c-6b5a-4321-9abc-ef0123456789",
"letterId": "1a2b3c4d-55aa-66bb-77cc-8899ddeeff00",
"remoteId": "a7Xp2Rz0KLMn",
"url": "https://kindverb.com/letter/a7Xp2Rz0KLMn",
"status": "success",
"contentHash": "b1f79e2a5c2b1a...<총64자리sha256>...",
"expiresAt": "2025-08-26T10:25:00Z",
"createdAt": "2025-08-19T10:25:05Z",
"updatedAt": "2025-08-19T10:25:05Z"
}
}

10.10 마이그레이션 전략

10.11 성능·용량 가드

10.12 개인정보 & 보안


11.API 개요 (API Overview - Auth, Endpoints, Errors, Rate)

11.1 철학 & 범위


11.2 인증(Auth) & 보안(Sec)


11.3 엔드포인트 일람(요약)

영역메서드/이름경로/컬렉션목적인증/보안Rate(클라이언트 수단)
Firestoreletters.createdb.collection("letters").add({...})편지 링크 공유(익명 쓰기)App Check, 보안규칙디바이스당 ≤20/일, ≤2/분
LLM(옵션)drafts.create/v1/drafts (프록시 or 공급자 SDK)비식별 프롬프트로 초안 생성API Key(앱 내 보관 금지: 키체인/원격설정)디바이스당 ≤60/일, ≤6/분
StoreKit영수증 검증(온디바이스)Pro 구매 상태 확인/복원Apple 서명Apple 한도(기본 제한 無)
App Checkattest무결성 토큰 발급/검증Firebase App Check필요 시 재요청 backoff
주의: Firestore “읽기”는 절대 제공하지 않습니다. 링크 페이지 조회는 웹(KindVerb.com) 에서만 가능하며 앱·서버는 해당 문서를 읽지 않습니다.

11.4 상세 스펙 — Firestore letters.create (유일한 원격 저장)

요청(Request) (클라이언트가 Firestore SDK로 add)

{
"content": "string (<=10000)", // 편지 원문 (최종본)
"createdAt": "serverTimestamp()" // 서버 타임스탬프 (클라이언트 입력 금지)
}

보안 규칙(Sample)

응답(Response)

에러(클라이언트 맵핑)


11.5 (옵션) LLM Draft API — 비식별 프록시 계약

개요

엔드포인트

요청(Request) JSON (비식별화된 상태)

{
"contents": [
{
"parts": [
{
"text": "Please draft a warm, empathetic message based on the following context:\nSituation: <PERSON_1> said something hurtful\nFeelings: [sad, disappointed]\nHurt: I felt ignored\nReflection: I may have overreacted\nNeed: I want to feel respected"
}
]
}
],
"generationConfig": {
"temperature": 0.7,
"topK": 40,
"topP": 0.95,
"maxOutputTokens": 800
}
}

응답(Response) JSON

{
"candidates": [
{
"content": {
"parts": [
{
"text": "I felt hurt when I was ignored. I may have overreacted, but what I need most is to feel respected..."
}
]
},
"safetyRatings": [
{ "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE" }
]
}
],
"usageMetadata": {
"promptTokenCount": 120,
"candidatesTokenCount": 95,
"totalTokenCount": 215
}
}

클라이언트 후처리

  1. 응답의 candidates[0].content.parts[0].text 추출.
  2. 토큰화된 엔터티(<PERSON_1>, <DATE_1>)를 역치환하여 원래 사용자 문맥 복원.
  3. LetterDraft 엔티티에 저장 (status="drafted", aiModel="gemini-2.5-flash-lite").

Rate Limit (앱 차원에서 강제)

오류 매핑 (현지화 Key 기반)

Gemini API 에러앱 내부 에러 도메인매핑 키 예시
401 Unauthorized (잘못된 API키)aierror.ai.unauthorized
429 Rate limitaierror.ai.rate_limited
400 Content policy violationaierror.ai.content_policy
504 Timeoutnetworkerror.network.timeout
기타/알 수 없음commonerror.common.unknown

11.6 인앱결제(StoreKit) 검증

11.7 레이트 리밋 & 백오프 (클라이언트 관점)

기능단위제한백오프
letters.create디바이스당20/일, 2/분1s→2s→4s… 최대 32s, 5회
drafts.create(옵션)디바이스당20/일, 6/분동일
App Check 재시도호출당3회2s 고정 간격
Firestore 자체 쿼터/스루풋 한계 발생 시에도 위 정책이 우선 적용되어 사용자 경험을 보호합니다.

11.8 에러 규약(통합 포맷)

{
"domain": "link|ai|iap|network|common",
"code": "permission_denied|rate_limited|timeout|verify_failed|unknown",
"messageKey": "error.link.rate_limited.message", // UI 표기용 현지화 키
"debugInfo": { "httpLike": 429, "provider": "firestore" } // 로그 전용(PII 금지)
}

11.9 보안·프라이버시 가드레일

11.10 계약 테스트(수용 기준)

11.11 변경/확장 여지


12.오프라인・동기화・캐싱 (Offline・Sync・Caching)

12.1 원칙 (Principles)

12.2 데이터 구분 (Data Residency & Classes)

데이터저장소동기화TTL/만료비고
Guided Journal(성찰 답변)SwiftData없음사용자가 삭제 시완전 오프라인
LetterDraft(편지 초안/완성)SwiftData없음사용자가 삭제 시완전 오프라인
LinkShare(공유 메타)SwiftData아웃박스 큐 → Firestore Create링크 자체는 Firestore에서 7일 TTL본문은 Firestore에만 존재(읽기 불가 정책)
Design/AI 설정(토큰/선호)UserDefaults(Keychain 아님)없음앱 재설치 시 소멸민감 정보 미보관
Gemini 호출 로그 메타OSLog없음시스템 보존 정책토큰/본문 금지

12.3 로컬 저장 구조 (SwiftData 모델 스냅샷)

실제 코드 구현은 Xcode에서 진행. 여기서는 스키마/필수 필드만 정의.
버전 필드는 데이터 마이그레이션/충돌 방지를 위한 미래 대비.

12.4 읽기/쓰기 경로 (Read/Write Path)

12.4.1 쓰기(오프라인)

  1. View → ViewModel이 Journal/LetterDraft즉시 로컬 저장
  2. 성공 시 UI 피드백(토스트/배지)
  3. 실패 시 도메인 에러 storage.write_failed를 발생 → 현지화 키로 사용자 안내

12.4.2 읽기

12.5 캐싱 정책 (Caching Policy)

12.6 링크 공유 동기화 (Outbox Sync)

12.6.1 개념

12.6.2 상태 머신

queued → uploading → (succeeded | failed)

12.6.3 재시도 정책

12.6.4 트리거

12.7 연결성 및 복구 UX (Connectivity & Recovery)

상황UI 안내(현지화 키 예시)사용자 선택지
오프라인 상태에서 링크 공유share.offline.queue_info (대기열에 저장됨, 연결 시 자동 전송)[확인]
업로드 실패(재시도 예정)share.retry.scheduled (곧 재시도)[닫기] [지금 다시 시도]
권한/정책 오류(400/401/403)share.failed.policy[가이드 보기] [닫기]
업로드 성공share.success.copied (URL 클립보드/공유시트)[공유하기] [닫기]
모든 버튼은 Button { } label: { } 형식, 모든 문구는 Localizable.xcstrings Key로만.

12.8 백그라운드 실행 (Background Tasks)

12.9 저장 한도 & 정리 (Storage Limits & Purge)

12.10 에러 도메인 & 매핑 (Error Domain & Mapping)

도메인코드의미사용자 안내 키
networkoffline네트워크 불가error.network.offline
networktimeout요청 시간 초과error.network.timeout
storagewrite_failedSwiftData 저장 실패error.storage.write_failed
shareunauthorizedFirestore 인증/키 오류error.share.unauthorized
sharerate_limited429error.share.rate_limited
sharepolicy_blocked400/403 정책 위반error.share.policy_blocked
commonunknown알 수 없음error.common.unknown

12.11 접근성/현지화 (A11y & L10n) — 오프라인 맥락 특화

12.12 보안·프라이버시(오프라인 관점)

12.13 성능 기준 (SLO) — 오프라인/캐시

항목목표비고
로컬 저장(write)p95 ≤ 60msJournal/LetterDraft
리스트 로드p95 ≤ 80ms50건 기준
이미지 렌더 캐시 조회p95 ≤ 40ms파일캐시 hit
아웃박스 전송 성공률≥ 99%24h 윈도우, 재시도 포함

12.14 텔레메트리(오프라인/동기화 관련)

이벤트 키속성트리거
share_task.createdletterId, queuedAt공유 버튼 탭 시
share_task.updatedtaskId, status, attempts, httpCode?상태 변경 시
share_task.succeededtaskId, expiresAt, urlHash성공 시
share_task.failedtaskId, lastErrorCode, attempts최종 실패 시
cache.prunedbytesFreed, kind캐시 청소 시
실제 사용자 노출 문구는 xcstrings 키로만 표시, 텔레메트리는 내부 스키마.

12.15 수용 기준 (Acceptance Criteria)

  1. 완전 오프라인에서: 성찰 입력 → 편지 작성/수정/저장까지 모든 경로 수행 가능.
  2. 오프라인에서 “링크로 공유”: ShareTask(queued) 생성, UI에 대기열 안내 표출.
  3. 온라인 복귀 시: 백그라운드/포그라운드/수동 재시도 중 하나 이상의 경로로 전송 시도.
  4. 정책 오류(401/403/400) 시: 재시도 중지 + 사용자 안내 + 도움말 링크(내부 가이드) 노출.
  5. 성공 시: URL 생성·만료일 계산(7일 후)·클립보드/공유시트 연결.
  6. 캐시 상한 초과 시: LRU로 삭제되고, UI는 오류 없이 동작.
  7. 모든 에러는 도메인/코드/현지화 키로 매핑되어 사용자에게 명확히 전달.

12.16 테스트 계획(샘플)


메모 (개발 시작용 체크)


13.보안・개인정보보호・컴플라이언스(Security・Privacy・Compliance)

13.1 원칙 & 범위

13.2 데이터 분류표 (Data Classification)

분류예시저장 위치보호 수준유지 기간
민감 사용자 생성물성찰 답변, 편지 초안/완성SwiftData(온디바이스)iOS 데이터 보호(NSFileProtection)사용자가 삭제 시까지
공유 본문링크로 공유된 편지 텍스트Firestore(서버)전송 중 TLS, 서버 저장 암호화7일 TTL 후 자동 삭제
동기화 메타ShareTask 상태, URL 해시SwiftData기기 내 보호상태 수명 + 7일 정리
설정/환경언어, 테마 등UserDefaults기기 내 보호앱 생명주기
비즈니스 로그(메타)전송 시도 횟수, HTTP 코드OSLog시스템 보호, 민감값 금지OS 정책 준수
설계 원칙: 이름/이메일/광고ID 등 식별자 비수집.

13.3 데이터 흐름(요약)

  1. 사용자가 성찰·편지를 작성 → SwiftData 로컬 저장
  2. “링크로 공유” 선택 시 → ShareTask(아웃박스) 생성 → 온라인 시 Firestore에 Create
  3. Firestore 문서 ID 기반 URL 발급 → 7일 후 자동 삭제(TTL)
  4. 서버에서 읽기/수정/삭제는 불가(규칙상 차단)

13.4 보안 통제(Technical Controls)

13.5 개인정보 보호(Privacy)

13.6 Firestore 보안 규칙 & TTL

13.7 권한(Privacy Prompts) & 현지화 키

모든 노출 문자열은 Localizable.xcstrings 키만 사용.

13.8 규정 준수 매핑(간단 가이드)

법률 자문이 필요한 경우 별도 검토. 위는 제품 설계 기준 준수 가이드.

13.9 인시던트 대응(Incident Response)

13.10 제3자 의존성 & 라이선스

13.11 보안/프라이버시 수용기준(AC)

  1. 식별자/추적자 비수집이 코드/설정/스토어 라벨로 일치한다.
  2. 앱은 완전 오프라인에서 핵심 기능이 동작한다.
  3. 링크 공유 시 사전 고지가 현지화 키로 표시되고, 사용자 동의 후에만 진행된다.
  4. Firestore는 Create만 성공, Read/Update/Delete는 실패한다(규칙 테스트 통과).
  5. Firestore 문서는 7일 후 자동 삭제가 검증된다(스테이징 환경 시뮬레이션).
  6. os.log민감 본문/토큰이 기록되지 않음이 점검된다.
  7. ATS 우회 없음(HTTP 금지), 허용 도메인만 접속 가능.
  8. 사진 권한 프롬프트 목적 문구가 현지화되어 표시된다.
  9. 프라이버시 정책(웹)과 앱 내 고지가 기능/데이터 흐름과 일치한다.
  10. 키 노출 가정하더라도 남용 감지/차단/교체 플랜이 문서화되어 있다.

13.12 테스트 계획(샘플)

13.13 위험 & 완화

위험설명완화
클라이언트 키 노출Secrets.plist는 리버스 엔지니어링에 취약최소 권한 키, 난독화, 주기적 롤테이션, 트래픽 모니터링/알림, 쿼터 설정
공유 링크 오남용URL이 타인에게 전달될 수 있음공유 전 경고·가이드, 민감정보 자제 권고, 7일 TTL, URL 난수 길이 확대
규칙 오구성Read 허용 등 오설정 위험IaC로 규칙 버전관리, CI에서 규칙 테스트, 변경 이중승인
로그 민감정보 유출개발자가 무심코 본문 로깅린터/리뷰 체크리스트, os_log 전용 래퍼로 민감값 차단
의료/치료 오인사용자가 치료 앱으로 인식앱/스토어 내 “정보성 가이드, 치료 아님” 고지, 문구 현지화

13.14 준비물 체크리스트(출시 전)


14.성능목표 & 품질기준 (Performance SLOs & Quality Bars)

14.1 핵심 성능 SLO (앱 전반)

항목정의목표(각 pctl)측정 방법도구/수집 주기
Cold Start TTI프로세스 시작 → 첫 상호작용 가능p50 ≤ 2.0s, p95 ≤ 3.0sXCTOSSignpostMetric.applicationLaunch, 실기기 리허설XCTest Performance, Instruments (매 스프린트)
Warm Start TTI백그라운드 → 포그라운드 인터랙션 가능p50 ≤ 0.9s, p95 ≤ 1.4sSignpost 마킹Instruments (주간)
스크롤 FPS주요 리스트/에디터 스크롤 프레임평균 ≥ 55fps, 드롭 프레임율 ≤ 1.5%Core Animation FPS, Hitch 추적Instruments (주간)
UI 응답성탭→UI 반응(하이라이트/전환)p95 ≤ 100msCustom signpost 구간UITest + Instruments
Crash-free 세션크래시 없는 세션 비율99.8%MXCrashDiagnostic, CrashlyticsMetricKit 일간 집계
Foreground Hang2s+ 메인스레드 정지 비율≤ 0.20% 세션MXHangDiagnosticMetricKit 주간
메모리 피크세션 중 최대 RSSp95 ≤ 350MBXCTMemoryMetric, MXMemoryMetricPerf 테스트/MetricKit
에너지 영향평균 에너지 등급Low 유지Energy Log 등급Instruments 리그(주간)
앱 크기(압축 IPA)App Store 배포 번들≤ 80MBXcode Archive 결과CI 빌드 게이트
TTI는 탭 가능 상태 진입 시점(첫 Interaction 가능한 상태)을 기준.

14.2 화면별 퍼포먼스 예산 (View Budget)

화면 ID1st PaintTTI추가 메모
V02 ConflictSelection≤ 300ms≤ 500ms캐러셀 이미지/일러스트는 비동기 지연 로드
V04 GuidedJournal (Q-step)≤ 200ms≤ 350ms키보드 표시 전 레이아웃 안정화
V06 MessageComposer≤ 350ms≤ 600ms초안 로딩 스켈레톤 ≤ 120ms 내 표시
V03 Paywall≤ 250ms≤ 400ms영수증 체크/상품 로드는 백그라운드
V07 Completion≤ 200ms≤ 300ms애니메이션 200–300ms 내 종료

14.3 네트워크/AI SLO (클라우드 1%)

항목정의목표(각 pctl)UX 정책측정
LLM 초안 생성V06 진입 후 첫 결과 수신 (Gemini 2.5 flash-lite)p50 ≤ 5.0s, p95 ≤ 9.0s스켈레톤 즉시(≤120ms), 7s 초과 시 친절한 안내 키(ai.timeout.tip)os_signpost, 이벤트
AI 재생성“다시 제안/부드럽게” 클릭→결과p50 ≤ 3.5s, p95 ≤ 7.0s버튼 상태: 로딩/비활성, 실패 시 재시도 키이벤트
링크 공유 CreateFirestore document 생성→URL 획득p50 ≤ 700ms, p95 ≤ 1.2s오프라인 시 아웃박스 큐, 온라인 시 자동 재전송이벤트 + Signpost
아웃박스 재전송 지연네트워크 복귀→전송 완료p50 ≤ 10s, p95 ≤ 60s백오프(최대 60s), 재시도 토스트이벤트

14.4 오프라인·저장소 SLO

항목정의목표측정/검증
SwiftData 저장 지연편지 저장 save() 호출→커밋p95 ≤ 120ms단위테스트 + Signpost
대용량 로컬셋2,000개 저널, 500개 편지V04/V06 TTI 목표 유지테스트 픽스처 리그
아웃박스 제한대기 중 ShareTask 수≤ 50개 (경고)상태 이벤트
백업 제외(선택)민감 데이터 iCloud 백업정책에 맞춰 제외 여부 명시파일 속성 점검

14.5 접근성·현지화 품질 바

영역기준(출시 게이트)측정/검증
A11y 라벨모든 상호작용 컨트롤 100% VoiceOver 라벨 (키 기반)XCUITest + 수동 점검
Dynamic TypeL~XXXL까지 레이아웃 깨짐 없음Snapshot 테스트
컬러 대비텍스트 대비 AA 이상Figma/Plugin, 수동
현지화 키 적용사용자 노출 문자열 100% 키 참조 (누락=빌드 실패)L10n 스크립트/CI
Pseudo-L10n길이 30% 증가 시 UI 유지스냅샷

14.6 코드 품질 바 (CI 게이트)

항목기준
컴파일 경고0
print 사용금지(검사 스크립트)
동시성 경고Swift Concurrency 경고 0
테스트 커버리지ViewModel/UseCase ≥ 70%, Service ≥ 60%
Lint/Formatting팀 규칙 준수(파일당 300라인 가이드)
스냅샷 테스트주요 화면(라이트/다크, 크기 2종) 통과
성능 회귀기준 대비 +10% 초과 증가 금지 (TTI/FPS/메모리)

14.7 실측·모니터링 설계

14.8 테스트 리그 & 시나리오

디바이스군
OS 범위: iOS 최신 안정버전 ±1 메이저
네트워크
데이터 볼륨
핵심 시나리오
  1. 콜드 스타트→V02→V04 입력→저장→V06 LLM 생성→편집→링크 공유(오프라인/온라인 전환)
  2. 다국어 전환(영/한), Dynamic Type XXL, 다크 모드
  3. 연속 세션 30분 스크롤/편집(메모리/에너지 관찰)

14.9 회귀 방지(성능 가드)

14.10 에러 예산 & 완화 정책

지표월간 에러 예산초과 시 조치
Crash-free 세션0.2%p새 기능 동결, Hotfix 48h 내 배포
Hang Rate0.2%메인스레드 트레이스 분석/해결 우선
LLM p95 지연9.0s프롬프트/토큰/네트워크 점검, 캐시/스트리밍 옵션 검토
링크 p95 지연1.2sFirestore 인덱스/리전/재시도 백오프 조정

14.11 출고 체크리스트 (Quality Bars)


15.분석&텔레메트리 (Analytics & Telemetry) 

15.1 목적 & 가드레일

15.2 데이터 등급 & 동의

등급예시수집/업로드 정책
Level 0 (필수 진단)앱 버전, OS, 기기 등급온디바이스 집계. 업로드는 Opt-in 시 익명화 후 전송
Level 1 (행태 이벤트)화면 진입, 버튼 탭, 퍼널 단계텍스트 키/코드만. 원문 텍스트 금지
Level 2 (성능/품질)TTI, FPS, Crash/HangMetricKit 표준 페이로드
금지(수집 안 함)‘마음 편지’ 원문, 감정 자유서술, 상대 실명/연락처항상 금지

15.3 세션/사용자 식별 규칙

15.4 이벤트 택소노미 (마스터 목록)

A) 앱/세션 라이프사이클

  1. app.install
  2. app.first_open
  3. session.start
  4. session.end

B) 온보딩/관계 선택

  1. onboarding.shown
  2. relationship.card_view
  3. relationship.select (props: type=friend|partner|parent|sibling|inlaw, locked=bool)

C) 저널(성찰 5단계)

  1. journal.step_view (props: step=1..5)
  2. journal.step_complete (props: step=1..5, input_type=tag|text, char_count)
  3. journal.finish

D) 메시지 작업실(LLM)

  1. draft.request (props: prompt_preset=initial|regenerate|softer|concise|sincere, estimated_tokens_in)
  2. draft.result (props: latency_ms, success=bool, retry_count, estimated_tokens_out)
  3. draft.edit_local (props: edit_chars, duration_ms)

E) 전달(공유)

  1. share.copy_text
  2. share.save_image (props: success=bool)
  3. share.link_create (props: success=bool, latency_ms, offline_queued=bool)
  4. share.link_open (웹(선택): 익명 파라미터만, 리퍼러/UA만)

F) 수익화

  1. paywall.view
  2. iap.purchase_attempt
  3. iap.purchase_result (props: success=bool, error_code)
  4. iap.restore_attempt
  5. iap.restore_result (props: success=bool)

G) 성능/품질 (요약 이벤트: 14장에서 정의한 SLO와 호응)

  1. perf.launch (cold/warm, tti_ms)
  2. perf.view_show (view_id, tti_ms)
  3. perf.ai_draft (preset, latency_ms, outcome)
  4. perf.share_create (latency_ms, outcome)
  5. perf.save_local (latency_ms, outcome)

H) 오류/복구

  1. error.shown (props: domain, code, retriable=bool)
  2. recovery.action (props: type=retry|offline_queue|open_settings)

I) 실험/플래그

  1. exp.exposure (props: exp_key, variant)
중요: 어떤 이벤트에도 사용자 원문 문자열을 넣지 않는다. 감정/메시지 내용은 길이·선택코드만.

15.5 핵심 이벤트 스키마 (샘플 표)

15.5.1 relationship.select

필드타입필수예시설명
typestring(enum)Y"partner"관계 유형 코드
lockedboolYfalsePro 잠금 여부
ab_test_variantstringN"A"실험군 라벨
tsint64Y1724212345678밀리초 타임스탬프

15.5.2 journal.step_complete

필드타입필수예시설명
stepintY2진행 단계(1..5)
input_typestring(enum)Y"tag"입력 타입
char_countintY120텍스트 길이(원문 미저장)
selected_tagsstring[]N["angry","hurt"]사전정의 코드
tsint64Y

15.5.3 draft.result

필드타입필수예시설명
prompt_presetstring(enum)Y"initial"요청 프리셋
latency_msintY3450왕복 지연
successboolYtrue성공 여부
estimated_tokens_inintN420요청 토큰 추정
estimated_tokens_outintN280응답 토큰 추정
error_codestringN"timeout"실패 시만

15.5.4 share.link_create

필드타입필수예시설명
successboolYtrue생성 성공
latency_msintY612Firestore 생성 시간
offline_queuedboolYfalse오프라인 큐 적재 여부

15.5.5 iap.purchase_result

필드타입필수예시설명
successboolYtrue결제 성공
error_codestringN"paymentCancelled"실패 시 StoreKit 코드
paywall_variantstringN"badgeA"페이월 실험군

15.6 퍼널 정의 (비즈니스 핵심)

15.6.1 핵심 가치 퍼널 (Activation)

session.startrelationship.select(type)
journal.step_complete(step=5)journal.finish
draft.request(preset=initial)draft.result(success=true)
share.copy_text|share.save_image|share.link_create(success=true)

15.6.2 수익화 퍼널

paywall.viewiap.purchase_attemptiap.purchase_result(success=true)

15.6.3 리텐션/재참여 퍼널

session.start (D+1/D+7/D+30 코호트) → journal.step_view ≥1 → draft.request ≥1

15.7 사용자 속성(익명) & 디바이스 속성

타입예시비고
app_versionstring"1.0.0"빌드/CFBundleShortVersionString
build_numberstring"100"
os_versionstring"iOS 18.0"
device_tierstring(enum)`"lowmid
localestring"en-US"
pro_statusboolfalseIAP 복원 후 업데이트
exp_bucketstring"A"플래그 SDK 없이도 저장

15.8 계측 아키텍처 & 파이프

대안(서버 없이): MetricKit + App Store Connect만으로 크래시/성능을 추적하고, 기능 이벤트는 **온디바이스 집계(카운터/퍼널 완료 플래그)**로만 사용해도 됨. (완전 프라이버시 극대화 모드)

15.9 샘플링 & 보존

15.10 QA & 검증(출시 게이트)

15.11 대시보드(권장 차트)

15.12 거버넌스 & 버저닝

15.13 개인정보 고지(설정 화면 복사 문구 제안)

15.14 (선택) 웹 링크 열람 텔레메트리

15.15 PRD 연계 맵


16.알림&인앱메시징 (Notifications & In-App Messaging)

16.1 목적 & 전략

16.2 가드레일

16.3 알림 유형 (로컬)

유형설명트리거 조건예시 메시지 키
Daily Reflection Reminder매일 성찰 유도매일 저녁 8시, 사용자 설정 시각notif.daily_reflection.body
Streak/N-Day Progress연속 기록 보상성찰 3일, 7일, 30일 달성 시notif.streak.body
Draft Follow-up미완성 초안 리마인드draft.request 후 24시간 내 share.* 없음notif.draft_followup.body
Share Celebration공유 완료 축하share.* 발생 직후notif.share_celebration.body
System Upgrade Info버전 업데이트 안내앱 버전 교체 후 최초 실행 시notif.upgrade_info.body

16.4 인앱 메시징 유형

유형설명트리거UI 형식
Onboarding Nudge알림 권한 요청 전 부드러운 안내첫 성찰 완료 직후하단 시트 + CTA
Feature Highlight새 기능 소개새 릴리스 후 첫 3세션풀스크린 모달
Paywall Inline Prompt잠금 기능 진입 시 업셀관계 카드 클릭 등풀스크린 Paywall
Upgrade CelebrationPro 업그레이드 후 환영 메시지iap.purchase_result(success=true) 직후모달 배너
Retention Win-back비활성 사용자 복귀 시7일 이상 미로그인 후 재실행배너 메시지

16.5 UX 설계 원칙

16.6 동의 UX (Consent UX)

  1. 앱 설치 → 온보딩 완료 → 첫 성찰 성공
  2. 인앱 시트:

16.7 기술 구현 (iOS)

16.8 텔레메트리 & 측정 (15장 연계)

16.9 빈도 & 지능형 제어

16.10 개인정보 보호 고려사항

16.11 향후 확장 (Roadmap)


17.수익화&가격정책 (Monetization & Pricing)

17.1 수익 철학 & 전략 요약

17.2 오퍼 구조 (Offer Structure)

17.2.1 무료(Free)

17.2.2 Pro (Lifetime – 1회성 결제)

17.2.3 선택적 확장 SKU(옵션, v1.1+)

출시 4~8주 뒤 ARPPU 상승을 위한 2차 업셀 포인트로 오픈

17.3 Paywall UX 설계 (윤리·전환 최적화)

현지화 키 예시

17.4 상품 정책 (App Store 가이드 준수)

17.5 가격 정책 & 실험 (Tiering & Tests)

측정 지표: Paywall 뷰율 → 결제 클릭률 → 결제 성공률 → 환불률, ARPPU, LTV, 지역별 전환

17.6 기능 게이팅 매트릭스 (Free vs Pro)

기능FreePro
관계 유형1종(친구 또는 동료)5종 전체
AI 초안1일 10회1일 20회
전문가 라이브러리미리보기 3개전체
마음 노트 고급미지원감정 통계/리마인더 커스텀
링크 공유기본(만료 7일)3/7/30일 선택

17.7 StoreKit 2 권한·영수증(온디바이스) 설계

현지화 키(결제 상태)

17.8 업셀 트리거 & 윤리 가드레일

17.9 텔레메트리 이벤트 & KPI (15장 연계)

핵심 이벤트
KPI

17.10 App Store 메타 & 외부 성장

17.11 수익 예측(샘플 모델 – 내부 참고)

실제는 지역·세율·환율에 영향 → AB 테스트로 전환 최적화 + 스토어 추천 노출로 탄력 확장

17.12 리스크 & 완화

17.13 수용 기준 (Acceptance Criteria)

17.14 현지화 키 패키지(요약)

17.15 로드맵(수익 확장)

부록 A) SKU 테이블 (초안)
Product ID유형현지화 이름 키가격(권장)설명 키
kindverb.pro.lifetime비소모성sku.pro_lifetime.title₩12,900sku.pro_lifetime.desc
kindverb.pack.season.2025q4비소모성sku.season_q4.title₩2,900sku.season_q4.desc
kindverb.pack.expert.family비소모성sku.expert_family.title₩3,900sku.expert_family.desc
부록 B) Gherkin(결제 코어 시나리오)
Feature: Pro Lifetime Purchase
Scenario: User unlocks all relations via lifetime purchase
Given the user taps a Pro-locked relation card
When the paywall is shown and the user confirms purchase of "kindverb.pro.lifetime"
Then the purchase succeeds and entitlement becomes "pro"
And the locked relations become accessible immediately
And an "entitlement.changed" event is logged with to=pro

Scenario: Restore purchases on new device
Given the user installs the app on a new device
When the user taps "Restore Purchases"
Then the app verifies transactions and sets entitlement to "pro" if found
And a success toast appears

실행 메모 (iOS 기술 가드레일 재확인)


18.실험(Experimentation - A/B, Feature Flags) 

18.1 목적 & 원칙

18.2 실험 타입

  1. 오프라인 A/B / A/B/n (기본): 디바이스 단위 무작위 배정, 결과는 온디바이스 집계 → TestFlight/내부 QA용 내보내기(수기 분석) + App Store Connect 지표와 교차 확인.
  2. 점진적 롤아웃(퍼센트 플래그): 신규 UX의 안정성 검증(5% → 25% → 100%).
  3. 시퀀셜 테스트(내부용): 기간을 분리해 전후 비교(데이터 적을 때 유용).
※ 서버 없는 구조이므로 글로벌 자동 밴딧은 사용하지 않음(디바이스 로컬 밴딧은 제품 전체 최적화에 불리).

18.3 코호트 배정(온디바이스, 결정적 해시)

PRD 키: exp.id, exp.name, exp.variant_keys = ["A","B","C"], exp.target = { region:"KR", lang:"ko|en", appVersion: ">=1.0.0" }, exp.rollout: 0..100.

18.4 기능 플래그(Feature Flags) 스키마

18.5 우선 실험 목록(가설·변형·지표)

E-01 페이월 카피 톤

E-02 페이월 노출 타이밍

E-03 가격 밴드 (KR)

E-04 AI 초안 톤

E-05 공유 버튼 순서

E-06 감정 선택 UI 레이아웃

모든 실험은 동시에 최대 2개만 노출(간섭 최소화).

18.6 측정 & 분석(§15 이벤트 매핑)

18.7 윤리·컴플라이언스 가드레일

18.8 운영 절차(End-to-End)

  1. 설계: 가설-지표-기간-표본 크기 정의(최소 7일, 주말 포함).
  2. 구현: Experiments.json 정의→플래그/배정 로직 연결→키 텍스트 추가(Localizable.xcstrings).
  3. QA: 디버그 화면에서 강제 배정/변형 미리보기, 로깅 확인.
  4. 롤아웃: 5% 퍼센트 플래그로 크래시/에러 모니터→25%→100%.
  5. 판정: 전환(주지표) + 가드레일 지표 동시 충족 시 승자 승격.
  6. 정리: 승자 변형을 기본값으로 고정, 실패 실험 플래그 제거.

18.9 수용 기준(AC)

18.10 현지화 키(예시)

18.11 Gherkin 시나리오(샘플)

Feature: On-device AB assignment
Scenario: Deterministic cohort on same device
Given a device with identifierForVendor = X
And experiment "exp_paywall_tone_v1" is active with split A:50 B:50
When the user starts the app
Then the app assigns a variant deterministically for this device
And the same variant is used across sessions

Feature: Paywall timing experiment
Scenario: Mini banner vs full screen
Given the user is Free and completes AI draft
When experiment "exp_paywall_timing_v1" assigns variant B
Then a mini banner is shown instead of immediate full-screen paywall
And analytics "paywall.viewed" includes experiment metadata

18.12 리스크 & 완화


19.수용기준&테스트계획 (Acceptance Criteria & Test Plan + Converage Mapping)

19.1 목적
19.2 수용기준 (Acceptance Criteria)
공통 AC

기능별 AC (예시)

저널링(성찰 기록)

AI 초안 생성

공유 & 페이월

알림 & 인앱 메시징

19.3 테스트계획 (Test Plan)

1) 단위 테스트 (XCTest)

2) UI 테스트 (XCUITest)

3) 수동 테스트 (QA)

4) 실험 검증

19.4 커버리지 매핑 (Coverage Mapping)

기능 영역Acceptance Criteria ID단위 테스트UI 테스트수동 QA자동화율
저널링(성찰 기록)JRN-01~0580%
AI 초안 생성AI-01~0490%
공유 & 페이월PAY-01~0685%
알림 & 인앱 메시징NOTI-01~03⚪️70%
데이터 모델 & 저장소DATA-01~04⚪️95%
보안 & 개인정보SEC-01~03⚪️75%

19.5 Definition of Done (DoD)


20.위험・가정・의존성 (Risks・Assumptions・Dependencies) 

20.1 목적 & 범위

20.2 리스크 분류 & 점수 체계

20.3 최상위 리스크 레지스터 (Top Risks Register)

ID유형리스크 설명LI점수조기 신호(Leading)완화책(Mitigation)비상대응(Playbook)소유자상태
R-01P핵심 가치 미전달로 D1/D7 리텐션 미달MedHighHigh1세션 내 AI초안 생성률 < 60%, 온보딩 이탈 > 35%온보딩 마찰 제거, 템플릿(오프라인) 즉시 노출, 빈 상태 가이드 강화페이월 노출 타이밍 연기, “즉시 결과 미리보기” 실험을 긴급 상시화PMOpen
R-02MPro 전환율 < 목표(3–5%)MedHighHigh페이월 노출 대비 결제 시도율 < 8%, 장바구니 이탈↑가격·베네핏 카피 A/B, 3일 내 ‘두 번째 기회’ 페이월, 번들 혜택 추가임시 10% 할인 캠페인(14일), Pro 체험 48h 제공 후 자동락GrowthOpen
R-03TLLM 지연/불가(Gemini 장애/쿼터)MedHighHighAI 응답 TTP > 6s, 오류율 > 5%오프라인 템플릿 즉시 대체, 재시도 지수백오프, 실패 키 UXAI 토글 Kill-switch, “오프라인 모드” 배너 및 템플릿로 대체iOS LeadOpen
R-04TFirestore TTL/한도 문제로 링크 만료/생성 실패LowMedMed링크 생성 실패율 > 1%, TTL 미적용 알림사전 쿼터 알람, TTL 규칙 e2e 테스트, 재시도 큐링크 기능 자동 비활성 + “텍스트 복사/이미지 저장”만 안내BackendMitigating
R-05SC민감 주제 앱 심사 리젝(정신건강 주장)MedHighHigh리젝 사유 4.8/1.1 언급, 메디컬 클레임 감지비치료/비진단 고지, 자기성찰 도구 명시, 위기 리소스 링크메디컬 표현 일괄 치환 스크립트, 앱 설명·스크린샷 즉시 수정PMOpen
R-06SC로컬 데이터 유출 위험(분실·탈옥)LowHighMed외부 백업 앱에서 DB 탐지AppTransportSecurity/데이터 보호 속성, Face/Touch ID 잠금 옵션긴급 보안 공지, 버그픽스 패치(핫픽스) + 사용법 가이드SecurityOpen
R-07TSwiftData 손상/마이그레이션 실패LowHighMed크래시 로그에 스키마 오류, 복구 실패 케이스마이그레이션 테스트 매트릭스/샌드박스 백업, 저널 자동 백업복구 마법사(백업→새 스키마 복원), 실패 시 내보내기 안내iOS LeadOpen
R-08O릴리스 지연(의존 태스크 슬립)MedMedMed번다운 차트 편차 > 25%, 리뷰 지연 > 3일모듈화·특성 플래그, 컷라인 관리, 위험기능 뒤로 밀기기능 플래그 Off로 빌드 고정, Hotfix 창구 분리PMMitigating
R-09M가격 민감도 과대평가/과소평가MedMedMed환불율 > 5%, 결제 전 이탈↑3단계 가격 테스트(₩9,900/12,900/15,000), 결제 전 가치 슬라이드단기 가격 롤백 + 구매자 보상(콘텐츠 추가/업그레이드)GrowthOpen
R-10SC현지화로 인한 오인/정서적 위해LowMedLowCS에 표현 오해 케이스 누적민감 키워드 리뷰, 톤·매뉴얼, 지역별 가이드문제 문구 핫패치(원격 키값 롤백), CS 스크립트 배포PMOpen
R-11T알림 권한 거부 → 리텐션 저하MedMedMed첫 24h 알림 허용률 < 35%맥락형 프리프롬프트, 가치 설명, 늦은 권한 재요청 UX푸시 의존 이벤트를 인앱 배너로 대체, 리마인더는 캘린더 내보내기iOSOpen
R-12T접근성(A11y) 미흡로 심사·리텐션 영향LowMedLowVoiceOver 이슈 리포트라벨·포커스 체계 테스트, 다크모드 대비 체크이슈 있는 뷰 임시 비노출/간소화 HotfixQAOpen
메모: 모든 리스크는 **실험(18장)**과 **분석(15장)**의 이벤트로 모니터링되어야 합니다. 각 R-ID는 이벤트 속성 risk_id로 태깅하여 대시보드에서 필터링 가능하게 설계합니다.

20.4 가정(Assumptions) & 검증 계획

ID가정(검증 필요 사실)근거/의도검증 방법 & 마감위반 시 대안
A-01iOS 16+ 점유율이 90%+SwiftUI 최신 구성요소 활용TestFlight 디바이스 분포 체크 (T-7)최소 타깃 iOS 15로 하향 고려(기능 일부 폴백)
A-02오프라인 우선으로도 핵심 루프 완결 가능프라이버시·속도“네트워크 끊김” 시나리오 QA 체크리스트템플릿 품질 보강 + 오프라인 편집 UX 강화
A-03₩12,900 가격의 수용성동종군 가격대A/B(₩9,900/12,900/15,000) 2주수익/전환 최적 포인트로 즉시 조정
A-04Firestore TTL 7일이 일관 동작문서 스펙스테이징에서 일 단위 TTL 삭제 확인앱 단에서 만료 태그 + 클라이언트 차단 로직
A-05Gemini 2.5 Flash Lite 안정성벤치/문서p95 응답 < 5s 모니터오프라인 템플릿 자동대체 + 큐 재시도
A-06심사 가이드라인 준수 시 승인사례/가이드프리심사 리뷰(문구 체크리스트)메디컬·치료 표현 일괄 수정 스크립트 가동
A-07로깅은 os.log만으로 충분운영 정책에러/성능지표 수집 확인추가 계측이 필요하면 Privacy-preserving counters 적용

20.5 의존성(Dependencies)

외부 의존성

내부 의존성

20.6 비상대응(Contingency) 플레이북

시나리오 S-01: LLM 장애/지연
시나리오 S-02: Firestore 문제(링크 생성 실패/TTL 불가)
시나리오 S-03: 심사 리젝(표현/포지셔닝 문제)
시나리오 S-04: 데이터 마이그레이션 실패

20.7 리스크 모니터링 지표 & 임계치(알람 기준)

지표임계치(경고/치명)소스액션
AI p95 응답시간4s / 6s성능 로그오프라인 템플릿 fallback 토글
AI 실패율3% / 5%에러 로그재시도 파라미터 상향 + 배너 공지
링크 생성 실패율0.5% / 1%기능 이벤트링크 비활성 + 대체 안내
D1 Retention30% / 25%분석온보딩·빈상태 가설 실험 가속
Pro 전환율3% / 2%결제 이벤트가격/카피 A/B 즉시 스케줄
알림 허용률45% / 35%시스템 이벤트권한 프리프롬프트 카피 교체

20.8 RAID(리스크·가정·이슈·의존성) 운영 템플릿

유형ID제목상태소유자업데이트(YYYY-MM-DD)다음 액션/마감
RiskR-03LLM 지연MitigatingiOS Lead2025-08-21Fallback 자동화 점검(08-22)
Assump.A-04TTL 7일OpenBackend2025-08-21스테이징 TTL 삭제 로그 캡처(08-23)
IssueI-02알림 허용률↓OpenPM2025-08-21권한 카피 A/B 배포(08-24)
Depend.D-01StoreKitOpeniOS Lead2025-08-21영수증 재검증 시나리오 테스트(08-25)

20.9 승인 기준(Exit Criteria)


21.출시계획&마일스톤(Release Plan & Milestones) 

21.1 전략적 목표

  1. **최소 기능 세트(MVP)**를 빠르게 출시하여 D1/D7 리텐션첫 결제 전환율을 검증
  2. App Store 리뷰 승인을 원활히 통과 → 심사 리스크 최소화
  3. **수익화 실험(A/B)**을 조기 삽입하여 가격·전환 데이터 확보
  4. 90일 내 안정적 운영 체계 완성 (AI 프록시, 오프라인 데이터 무결성, 결제·심사 대응 자동화)

21.2 단계별 계획

🟢 Phase 1 — 준비 단계 (T-60 ~ T-30)

📌 마일스톤 M1 (T-30)
🟡 Phase 2 — 알파 & 베타 (T-30 ~ T-14)
📌 마일스톤 M2 (T-14)
🟠 Phase 3 — 심사 & 출시 준비 (T-14 ~ T-0)
📌 마일스톤 M3 (T-7)
📌 마일스톤 M4 (T-0)
🔵 Phase 4 — 안정화 & 성장 (T+1일 ~ T+90일)
📌 마일스톤 M5 (T+30)
📌 마일스톤 M6 (T+90)

21.3 주요 KPI 타임라인

21.4 실행 원칙

  1. 출시일 고정 → 기능 플래그 조정 (컷라인 관리)
  2. 심사 리스크 대비 → 메디컬 표현 제거 자동화
  3. 수익화 조기 검증 → 결제·가격 A/B 빠르게
  4. 오프라인 완결 UX 확보 → LLM 장애와 무관하게 사용 가능

22.범위 외 항목 (Out of Scope) 

22.1 이번 릴리스에서 제외되는 기능

22.2 의도적으로 제외한 UX 영역

22.3 수익화 관련 제외 범위

22.4 기술적 범위 외

22.5 정리


23. 용어집 (Glossary)

용어정의비고
KindVerb앱의 정식 명칭. “친절한 말하기”를 돕는 iOS 전용 앱.코드/문서에서 프로젝트명으로 사용.
MVP (Minimum Viable Product)최소 기능 제품. 빠른 출시와 학습을 위해 꼭 필요한 기능만 담은 첫 버전.1.0 출시 목표.
SwiftUIApple의 선언형 UI 프레임워크. 모든 화면(View)은 SwiftUI로 작성.UIKit 사용 금지.
MVVM (Model-View-ViewModel)앱 아키텍처 패턴. View ↔ ViewModel ↔ Model 레이어 구분.iOS Best Practice 강제.
Swift Concurrencyasync/await 기반의 최신 비동기 처리 방식.completion handler 금지.
Error HandlingNSError 대신 Swift Error 프로토콜 기반의 throw/try/catch.표준화된 도메인 에러 키 사용.
Localization (현지화)모든 사용자 노출 텍스트는 Localizable.xcstrings 키 기반.기본 언어: 영어.
Telemetry (텔레메트리)앱 내 이벤트/지표 로깅. 사용자 행동/성능 지표 수집.Analytics와 함께 사용.
SLO (Service Level Objective)성능 및 안정성 목표치. 예: 앱 실행 ≤ 2초.QA와 출시 심사 기준.
Offline-First네트워크 연결 여부와 무관하게 앱이 정상 작동하도록 설계.SwiftData 로컬 저장 필수.
Firestore (TTL 7일)구글 Firebase Firestore. KindVerb에서는 **공유 링크 저장소(익명)**로만 사용.7일 후 자동 만료.
LLM (Large Language Model)대규모 언어 모델. KindVerb에서는 Gemini 2.5 Flash-Lite를 사용.프록시 계약 통해 익명 호출.
Secrets.plist민감 API 키 저장용 설정 파일. Git에 포함되지 않음.GeminiAPIKey 필수.
Button Format RuleSwiftUI 버튼 작성 규칙: Button { /* action */ } label: { /* view */ }다른 형식 금지.
JTBD (Jobs To Be Done)“When ___, I want to ___, so I can ___” 형태의 사용자 과업 정의.항상 영문으로 유지.
App Flow (앱플로우)앱 화면 간 이동/전환 경로 정의.정보구조(IA)와 연결.
Scope Creep계획되지 않은 기능이 개발 범위에 추가되는 현상.Out of Scope 선언으로 방지.
In-App Purchase (IAP)앱 내 결제 기능. 구독/일회성 결제 포함.KindVerb의 핵심 수익화 모델.
Feature Flags기능의 On/Off를 제어하는 스위치. 점진적 출시(A/B 실험)에 활용.빠른 롤백 가능.
Acceptance Criteria (수용 기준)특정 기능이 완료되었음을 판단할 수 있는 구체적 문장.QA 및 테스트 기준.
Milestones (마일스톤)프로젝트 주요 시점/단계별 목표.Alpha → Beta → Launch.

📌 특징

24.AI프롬프트 엔벨로프 (AI Prompt Envelope - GPT5 Optimized Block) 

24.1 목적


24.2 System Instruction (AI에게 전달할 시스템 역할)

You are an expert iOS engineer. 
Generate SwiftUI + MVVM code using Swift Concurrency (async/await) only.
Do not use completion handlers. Do not use NSError.
Use Swift Error protocol with throw/try/catch for error handling.
All user-facing strings must use localization keys from Localizable.xcstrings.
In code, reference keys only (e.g., Text("home.title")).
All Buttons must be written strictly as:
Button { /* action */ } label: { /* view */ }.
Never use Button("label") { ... } form.
Adopt Offline-first principle: All user data must persist on-device using SwiftData.
Cloud communication allowed only for link sharing (Firestore TTL=7 days).
When AI drafting is needed, use gemini-2.5-flash-lite model via Secrets.plist with key "GeminiAPIKey".
Logging must use os.log (unified logging), never print().
Provide production-ready, modular code, with folder structure and per-file explanations.
Ask clarification questions before coding if any PRD detail is missing.

24.3 Inputs (AI가 받을 입력)

24.4 Outputs (AI가 반드시 생성해야 할 결과물)

  1. Source Code
  2. Folder Tree
  3. Unit Tests
  4. App Flow Glue

24.5 Example Prompt Block (AI 호출 시 붙여넣기용)

SYSTEM / INSTRUCTION
[위의 System Instruction 블록 붙여넣기]

INPUTS
- Full PRD (sections 1–26)
- Design tokens (Section 8)
- Data models & API schemas (Sections 10–11)

OUTPUTS
- SwiftUI + MVVM code (with async/await)
- Full folder tree & explanations
- Unit tests (ViewModel, UseCase)
- All code must follow: Button rule, Localization rule, Error rule

24.6 왜 중요한가? (JOHS 관점에서)


25.핸드오프 템플릿 (Handoffs Templates)

25.1 Backend Structure Document (백엔드 구조 문서)

목적: KindVerb 앱이 의존하는 서버 API를 팀이 일관되게 구현/확장할 수 있도록 표준 구조 제공
템플릿 구조 예시

25.2 Frontend Guidelines Document (프론트엔드 가이드라인)

목적: SwiftUI + MVVM 기반으로 개발자 전원이 동일한 코드 규칙을 준수하도록 강제
템플릿 구조 예시

25.3 Feature Specification Document (기능 명세서)

목적: PRD의 Feature Spec을 더 상세화하여 에지 케이스 & 복구 전략까지 반영
템플릿 구조 예시

25.4 App Flow Document (앱 플로우 문서)

목적: 팀원 모두가 앱의 전체 네비게이션·상태 전환을 한눈에 볼 수 있도록
템플릿 구조 예시

25.5 Implementation Plan (구현 계획)

목적: 주차별 일정/담당/위험/디펜던시를 명확히 하여 실제 실행 단계에서 혼선을 방지
템플릿 구조 예시

25.6 QA & Test Handoff (QA 전달 문서)

목적: QA가 “무엇을 어떻게 테스트해야 하는지”를 명확히 알 수 있도록
템플릿 구조 예시

25.7 Marketing & Store Handoff (마케팅/스토어 전달 문서)

목적: 마케팅팀/스토어 운영팀이 앱스토어 등록 및 언론 대응을 할 수 있도록
템플릿 구조 예시

26.PRD 품질 체크리스트 (AI & Human) 

26.1 Human Review Checklist (인간 검수용)

카테고리질문체크 여부
목표PRD의 문제 정의와 목표가 명확히 구분되어 있는가?
범위In-Scope / Out-of-Scope가 구체적으로 작성되어 있는가?
사용자Personas와 JTBD가 현실성 있고 구체적으로 서술되어 있는가?
스토리User Story가 감정적으로 설득력 있으며, App Store Description까지 이어졌는가?
데이터 모델모든 엔티티(Entity)와 관계(1:N, N:N)가 정의되었는가?
APIAuth, Endpoints, Error Domain, Rate Limit이 명확히 서술되었는가?
UI/UX모든 사용자 노출 텍스트가 Localization 키 기반으로 작성되었는가?
기술 가드레일SwiftUI, MVVM, async/await, Error Handling 규칙이 명시되었는가?
보안/개인정보암호화, TTL 정책, 로컬 저장, 컴플라이언스 대응이 있는가?
수익화Pricing 모델과 결제 흐름이 App Store 정책에 맞게 정의되었는가?
실험 설계A/B 테스트와 Feature Flags 조건이 정의되었는가?
출시 계획마일스톤과 QA/스토어 핸드오프 플랜이 포함되어 있는가?

26.2 AI Review Checklist (AI 친화성 검수용)

카테고리질문체크 여부
명확성모든 주요 기능은 Gherkin 문법(Scenario: Given-When-Then) 또는 Acceptance Criteria로 표현되었는가?
일관성버튼 작성 규칙, 에러 처리 방식, 현지화 규칙이 문서 전체에서 일관되게 적용되었는가?
데이터모든 JSON 예시가 유효한 구조(올바른 key:value)로 제공되는가?
LLM 지침AI Prompt Envelope(24번)이 명확히 정의되어 있는가?
오프라인 우선SwiftData 저장, Firestore TTL, Sync 정책이 누락 없이 기술되었는가?
성능 기준SLO(SLOs)와 Quality Bars가 수치형으로 정의되어 있는가?
테스트 가능성모든 Acceptance Criteria는 QA 테스트 케이스로 변환 가능하게 작성되었는가?
코드 자동 생성AI가 PRD만 보고 폴더 트리 + SwiftUI 코드 + 테스트 코드를 자동 생성할 수 있는가?
중복성 제거동일 항목이 여러 곳에서 다르게 기술되지 않았는가?

26.3 Meta Review Checklist (최종 메타 검증)

26.4 활용 방법

  1. PRD 최종 작성 후, Human Reviewer(기획자, 개발자, 디자이너, 마케터)가 26.1 체크리스트를 검토
  2. AI(GPT-5 또는 Gemini)에게 26.2 체크리스트 기준으로 “PRD 평가”를 요청 → AI가 누락/모호성을 지적
  3. PRD 보완 → 다시 Human & AI가 교차 검토 → Meta Review 체크 완료
  4. 최종 승인 시 **“AI Prompt Envelope”**와 함께 코드 생성 단계로 진입