From 5d3a5ac8acc3592c8e800e595275d034cbd95886 Mon Sep 17 00:00:00 2001
From: corpi
Date: Sat, 14 Mar 2026 19:45:55 +0900
Subject: [PATCH] =?UTF-8?q?feat(stats):=20pro=20personalized=20handoff=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/12_core_loop_execution_roadmap.md | 5 ++++-
docs/90_current_state.md | 4 ++++
docs/session_brief.md | 5 ++++-
docs/work.md | 3 ++-
src/features/stats/model/useFocusStats.ts | 4 ++++
src/shared/i18n/messages/app.ts | 2 ++
.../focus-dashboard/ui/FocusDashboardWidget.tsx | 17 ++++++++++++-----
.../stats-overview/ui/StatsOverviewWidget.tsx | 16 +++++++++++++++-
8 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/docs/12_core_loop_execution_roadmap.md b/docs/12_core_loop_execution_roadmap.md
index 69c8c31..d877c12 100644
--- a/docs/12_core_loop_execution_roadmap.md
+++ b/docs/12_core_loop_execution_roadmap.md
@@ -328,7 +328,10 @@ Away / Return이 끼어들기 전, 다음으로 예정된 축은 아래 두 가
- `Weekly Review Entry Flow` Slice 2
- `/stats` 마지막 CTA가 `/app?review=weekly&carryHint=...` handoff로 연결
- `/app`은 review-aware return hint를 먼저 보여주고, goal 입력은 그대로 사용자가 결정한다
- - 다음 구현은 Pro personalized handoff
+- `Weekly Review Entry Flow` Slice 3
+ - Pro에서는 `/stats` carry-forward에 추천 ritual을 함께 보여준다
+ - `/stats` 마지막 CTA와 `/app` return hint가 더 구체적인 next-session handoff로 바뀐다
+ - 다음 구현은 `/space` secondary review teaser
---
diff --git a/docs/90_current_state.md b/docs/90_current_state.md
index f9fa614..9cc55a8 100644
--- a/docs/90_current_state.md
+++ b/docs/90_current_state.md
@@ -121,6 +121,10 @@ Last Updated: 2026-03-14
- `/stats` 마지막 CTA는 `/app?review=weekly&carryHint=...&entryPreset=forest-50-10`으로 연결된다
- `/app`은 이 query를 받아 hero 위에 review-aware return hint를 노출한다
- goal과 microStep은 자동 입력하지 않고, 방향만 가볍게 제안한다
+- Pro personalized handoff 3차 연결:
+ - Pro에서는 `/stats` carry-forward 섹션에 추천 ritual을 함께 보여준다
+ - `/stats` 마지막 CTA 카피가 generic start가 아니라 `가장 잘 맞은 ritual로 /app 돌아가기`로 바뀐다
+ - `/app` teaser와 review return hint도 Pro에서 더 구체적인 next-session handoff 톤으로 표시된다
- paywall / plan / landing 메시지 재정렬:
- paywall 가치 포인트를 multi-queue, rituals, weekly review 중심으로 재작성
- landing pricing에서 구현되지 않은 1:1 매칭 / 오픈 코워킹 / 팀 대시보드를 메인 판매 포인트에서 제거
diff --git a/docs/session_brief.md b/docs/session_brief.md
index 78ac0bf..445f779 100644
--- a/docs/session_brief.md
+++ b/docs/session_brief.md
@@ -89,7 +89,10 @@ Last Updated: 2026-03-14
- `/stats` 마지막 CTA의 `/app` return handoff가 연결됐다.
- carry-forward CTA는 `/app?review=weekly&carryHint=...`로 돌아온다.
- `/app`은 review-aware return hint를 먼저 보여주되, goal은 사용자가 직접 입력하게 유지한다.
- - 다음 구현은 Pro personalized handoff다.
+- `Weekly Review Entry Flow`의 Pro personalized handoff까지 연결됐다.
+ - Pro에서는 `/stats` carry-forward에 추천 ritual을 함께 보여준다.
+ - `/stats` 마지막 CTA와 `/app` teaser / return hint가 더 구체적인 handoff 톤으로 바뀐다.
+ - 다음 구현은 `/space` complete 이후 secondary review teaser다.
- 유료화 포지셔닝을 `Calm Session OS`로 재정의했다.
- Free는 기본 집중 시작, Pro는 더 잘 이어가기라는 메시지로 정리했다.
- old `Scene Packs / Sound Packs / Profiles` 중심 카피를 `Daily plan / Rituals / Weekly review` 구조로 교체했다.
diff --git a/docs/work.md b/docs/work.md
index c2c6c2a..3341bed 100644
--- a/docs/work.md
+++ b/docs/work.md
@@ -110,7 +110,8 @@
- Slice 1 완료: `/app` hero 아래 low-emphasis weekly review teaser 추가
- Slice 2 완료: `/stats` 마지막 CTA가 `/app?review=weekly&carryHint=...` handoff로 연결
- `/app`은 query를 받아 review-aware return hint를 먼저 보여준다
- - 다음 slice: Pro personalized handoff
+ - Slice 3 완료: Pro에서 추천 ritual과 더 구체적인 CTA / return hint가 연결된다
+ - 다음 slice: `/space` complete 이후 secondary review teaser
- 검증:
- `/app -> /stats -> /app` 실제 브라우저 플로우 확인
- hero와 teaser의 시각 우선순위 확인
diff --git a/src/features/stats/model/useFocusStats.ts b/src/features/stats/model/useFocusStats.ts
index a068017..5c0eca1 100644
--- a/src/features/stats/model/useFocusStats.ts
+++ b/src/features/stats/model/useFocusStats.ts
@@ -80,6 +80,8 @@ export interface WeeklyReviewViewModel {
completionQuality: WeeklyReviewSection;
carryForward: {
hintKey: ReviewCarryHint;
+ presetId: string;
+ presetLabel: string;
keepDoing: string;
tryNext: string;
ctaLabel: string;
@@ -176,6 +178,8 @@ const buildCarryForward = (summary: FocusStatsSummary): WeeklyReviewViewModel['c
return {
hintKey,
+ presetId: 'forest-50-10',
+ presetLabel: 'Forest · 50/10 · Forest Birds',
keepDoing,
tryNext,
ctaLabel: copy.stats.reviewCarryCta,
diff --git a/src/shared/i18n/messages/app.ts b/src/shared/i18n/messages/app.ts
index 93407b6..a74a35a 100644
--- a/src/shared/i18n/messages/app.ts
+++ b/src/shared/i18n/messages/app.ts
@@ -73,8 +73,10 @@ export const app = {
reviewCarryTryClosure: '시작은 있었지만 마무리가 약했어요. 다음 주에는 완료 직전에 다른 블록으로 넘어가지 않는 흐름을 한 번 만들어 보세요.',
reviewCarryTryStart: '시작 횟수가 적었어요. 다음 주에는 길이를 늘리기보다 첫 세션을 한 번 더 여는 것에 집중해 보세요.',
reviewCarryCta: '이 흐름으로 다음 세션 시작',
+ reviewCarryCtaPro: '가장 잘 맞은 ritual로 /app 돌아가기',
reviewCarryKeepTitle: '다음 주에 유지할 것',
reviewCarryTryTitle: '다음 주에 바꿔볼 것',
+ reviewCarryPresetLabel: '추천 ritual',
today: '오늘',
last7Days: '최근 7일',
chartTitle: '집중 흐름 그래프',
diff --git a/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx b/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx
index ee39df4..da0b5ad 100644
--- a/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx
+++ b/src/widgets/focus-dashboard/ui/FocusDashboardWidget.tsx
@@ -42,6 +42,9 @@ const entryCopy = {
reviewTitle: '이번 주 review를 잠깐 보고 갈까요?',
reviewCta: '주간 review 보기',
reviewHelper: '다음 세션 전에 가볍게 보고 갈 수 있어요.',
+ reviewTitlePro: '나에게 잘 맞았던 흐름을 다시 보고 갈까요?',
+ reviewCtaPro: '나에게 맞는 흐름 보기',
+ reviewHelperPro: '가장 잘 맞았던 ritual과 carry-forward를 보고 돌아올 수 있어요.',
reviewReturnEyebrow: '방금 본 review 기준',
reviewReturnTitleSteady: '이번 주에 잘 맞았던 흐름을 그대로 가져가 보세요.',
reviewReturnTitleSmaller: '이번엔 목표를 더 작게 잡아보세요.',
@@ -148,7 +151,11 @@ export const FocusDashboardWidget = () => {
const reviewReturnCopy =
normalizedReviewCarryHint !== null ? reviewCarryCopyByHint[normalizedReviewCarryHint] : null;
const reviewReturnRitualLabel =
- reviewEntryPreset === 'forest-50-10' ? entryCopy.reviewReturnRitualLabel : null;
+ isPro && reviewEntryPreset === 'forest-50-10' ? entryCopy.reviewReturnRitualLabel : null;
+ const reviewTeaserTitle = isPro ? entryCopy.reviewTitlePro : entryCopy.reviewTitle;
+ const reviewTeaserSummary = isPro ? review.carryForward.keepDoing : review.snapshotSummary;
+ const reviewTeaserHelper = isPro ? entryCopy.reviewHelperPro : entryCopy.reviewHelper;
+ const reviewTeaserCta = isPro ? entryCopy.reviewCtaPro : entryCopy.reviewCta;
useEffect(() => {
let cancelled = false;
@@ -417,15 +424,15 @@ export const FocusDashboardWidget = () => {
{entryCopy.reviewEyebrow}
- {entryCopy.reviewTitle}
+ {reviewTeaserTitle}
- {review.snapshotSummary}
+ {reviewTeaserSummary}
- {entryCopy.reviewHelper}
+ {reviewTeaserHelper}
- {entryCopy.reviewCta}
+ {reviewTeaserCta}
diff --git a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx
index 70d14e7..dc692ce 100644
--- a/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx
+++ b/src/widgets/stats-overview/ui/StatsOverviewWidget.tsx
@@ -1,6 +1,7 @@
'use client';
import Link from 'next/link';
+import { usePlanTier } from '@/entities/plan';
import { useFocusStats } from '@/features/stats';
import { copy } from '@/shared/i18n';
import { cn } from '@/shared/lib/cn';
@@ -79,7 +80,9 @@ const ReviewSection = ({
export const StatsOverviewWidget = () => {
const { stats } = copy;
+ const { isPro } = usePlanTier();
const { review, isLoading, error, source, refetch } = useFocusStats();
+ const carryForwardCtaLabel = isPro ? stats.reviewCarryCtaPro : review.carryForward.ctaLabel;
return (
@@ -193,11 +196,22 @@ export const StatsOverviewWidget = () => {
+ {isPro ? (
+
+
+ {stats.reviewCarryPresetLabel}
+
+
+ {review.carryForward.presetLabel}
+
+
+ ) : null}
+
- {review.carryForward.ctaLabel}
+ {carryForwardCtaLabel}