diff --git a/app/(shell)/focus/FocusModeClient.tsx b/app/(shell)/focus/FocusModeClient.tsx index 0d824d61..b9b95456 100644 --- a/app/(shell)/focus/FocusModeClient.tsx +++ b/app/(shell)/focus/FocusModeClient.tsx @@ -169,6 +169,8 @@ interface FocusModeClientProps { founderReadiness?: FounderReadiness | null; /** Owner-facing First-Light readiness (ALL owners) — drives the momentum strip. */ firstLightReadiness?: FirstLightReadiness | null; + /** F2: brief continuity — carry-over since the owner's last visit (counts only). */ + briefContinuity?: { snoozedCount: number; awaitingReplyCount: number } | null; } type ActionDecision = "approved" | "skipped" | "dismissed"; @@ -269,6 +271,7 @@ export function FocusModeClient({ isFounder = false, founderReadiness = null, firstLightReadiness = null, + briefContinuity = null, }: FocusModeClientProps) { const router = useRouter(); const copy = getVerticalCopy(vertical); @@ -926,6 +929,29 @@ export function FocusModeClient({ {!isFounder && firstLightReadiness && ( )} + {/* F2: brief continuity — the brief "remembers" overnight. Static, counts only, + renders ONLY when there's real carry-over (no clutter on an empty board). */} + {briefContinuity && + (briefContinuity.awaitingReplyCount > 0 || briefContinuity.snoozedCount > 0) && ( +
+ Since you were last here + {briefContinuity.awaitingReplyCount > 0 && ( + + + {briefContinuity.awaitingReplyCount} awaiting reply + + )} + {briefContinuity.snoozedCount > 0 && ( + + + {briefContinuity.snoozedCount} snoozed, coming back + + )} +
+ )} {simpleView ? ( <> {/* Simple mode (reframe v2): greeting + the Single-Action Queue ONLY - ONE diff --git a/app/(shell)/focus/page.tsx b/app/(shell)/focus/page.tsx index be550a6b..3af3704a 100644 --- a/app/(shell)/focus/page.tsx +++ b/app/(shell)/focus/page.tsx @@ -248,29 +248,49 @@ export default async function FocusPage({ searchParams }: { searchParams: Search let clientCount = 0; let pendingRecoveryCount = 0; let approvedCount = 0; + let snoozedCount = 0; + let awaitingReplyCount = 0; if (profile?.tenant_id) { - const [clientCountRes, pendingCountRes, approvedCountRes] = await Promise.all([ - supabase - .from("pulse_clients") - .select("id", { count: "exact", head: true }) - .eq("tenant_id", profile.tenant_id), - supabase - .from("recovery_items") - .select("id", { count: "exact", head: true }) - .eq("tenant_id", profile.tenant_id) - .not("status", "in", '("approved","dismissed")'), - supabase - .from("recovery_items") - .select("id", { count: "exact", head: true }) - .eq("tenant_id", profile.tenant_id) - .eq("status", "approved"), - ]); + const [clientCountRes, pendingCountRes, approvedCountRes, snoozedCountRes, sentCountRes] = + await Promise.all([ + supabase + .from("pulse_clients") + .select("id", { count: "exact", head: true }) + .eq("tenant_id", profile.tenant_id), + supabase + .from("recovery_items") + .select("id", { count: "exact", head: true }) + .eq("tenant_id", profile.tenant_id) + .not("status", "in", '("approved","dismissed")'), + supabase + .from("recovery_items") + .select("id", { count: "exact", head: true }) + .eq("tenant_id", profile.tenant_id) + .eq("status", "approved"), + // F2: brief continuity — items the owner snoozed (coming back) ... + supabase + .from("recovery_items") + .select("id", { count: "exact", head: true }) + .eq("tenant_id", profile.tenant_id) + .eq("status", "snoozed"), + // ... and items already sent, awaiting a client reply. + supabase + .from("recovery_items") + .select("id", { count: "exact", head: true }) + .eq("tenant_id", profile.tenant_id) + .eq("status", "sent"), + ]); clientCount = clientCountRes.count ?? 0; pendingRecoveryCount = pendingCountRes.count ?? 0; approvedCount = approvedCountRes.count ?? 0; + snoozedCount = snoozedCountRes.count ?? 0; + awaitingReplyCount = sentCountRes.count ?? 0; } const firstLightAchieved = lifetimeRecoveredCents > 0; const firstLightReadiness = { clientCount, pendingRecoveryCount, approvedCount, firstLightAchieved }; + // F2: brief continuity — the brief "remembers" overnight so the owner sees carry-over, + // not just a flat list (counts only, honest). + const briefContinuity = { snoozedCount, awaitingReplyCount }; // Founder panel keeps its exact shape + founder-only gating. const founderReadiness: | { pulseConnected: boolean; clientCount: number; pendingRecoveryCount: number; firstLightAchieved: boolean } @@ -425,6 +445,7 @@ export default async function FocusPage({ searchParams }: { searchParams: Search isFounder={isFounder} founderReadiness={founderReadiness} firstLightReadiness={firstLightReadiness} + briefContinuity={briefContinuity} /> );