Skip to content

Commit fc0d189

Browse files
authored
Merge pull request #22 from cortexark/fix/coach-plan-signin-productionize
Productize coach plan and restore Apple sign-in capability
2 parents 0d7ee63 + cf51950 commit fc0d189

20 files changed

Lines changed: 459 additions & 361 deletions
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Thump Customer Value Plan
2+
3+
## Honest Product Read
4+
5+
Thump is promising more than it has proven.
6+
7+
Right now the strongest customer value is not "AI wellness" or "family heart platform." It is one narrow job:
8+
9+
- help me decide whether today should be a push day, a steady day, or a recovery day
10+
11+
That is the habit worth building around.
12+
13+
What is weak today:
14+
15+
- too much product breadth for the current depth
16+
- trust asks happen before value is obvious
17+
- monetization existed in code before it existed in the customer story
18+
- family and multi-tier packaging overstate what is actually useful today
19+
20+
## Most Useful Feature For Customers
21+
22+
The most useful feature is the morning readiness decision:
23+
24+
- one quick snapshot
25+
- one plain-language recommendation
26+
- one reason why
27+
28+
The customer does not primarily want "more metrics."
29+
The customer wants:
30+
31+
- confidence about how hard to train today
32+
- a clear signal when recovery is slipping
33+
- a simple weekly recap that helps them adjust behavior
34+
35+
## Keep / Cut / Delay
36+
37+
### Keep
38+
39+
- daily readiness / recovery snapshot
40+
- plain-language coaching recommendation
41+
- stress context when it changes today's recommendation
42+
- weekly review
43+
- PDF summary for users who want to share progress
44+
45+
### Cut From Marketing
46+
47+
- Family plan positioning
48+
- caregiver claims
49+
- broad "full insights" language with no concrete result
50+
- any implication that the app is a medical evaluator
51+
52+
### Delay
53+
54+
- family dashboard
55+
- caregiver workflows
56+
- advanced multi-member features
57+
- broad subscription ladders
58+
59+
## Customer Journey To Build Around
60+
61+
### First 60 Seconds
62+
63+
The customer should be able to answer:
64+
65+
- what is my state today?
66+
- what should I do with that information?
67+
68+
### First Week
69+
70+
The customer should build a habit:
71+
72+
- open every morning
73+
- check the recommendation
74+
- notice one useful pattern
75+
76+
### First Month
77+
78+
The customer should feel:
79+
80+
- this catches overreaching earlier than I do
81+
- this helps me train more consistently
82+
- this is worth paying for because it changes decisions
83+
84+
## Product Strategy
85+
86+
### Free
87+
88+
- daily snapshot
89+
- basic trends
90+
- watch check-ins
91+
92+
### Coach
93+
94+
- full metrics dashboard
95+
- stress and anomaly context
96+
- weekly review
97+
- deeper multi-week trends
98+
- PDF wellness summaries
99+
100+
## 30 / 60 / 90 Day Build Plan
101+
102+
### First 30 Days
103+
104+
- make the daily snapshot the clearest part of the app
105+
- tighten sign-in and trust copy
106+
- remove stale marketing around Family and legacy pricing
107+
- add paywall triggers at real premium boundaries
108+
109+
### Next 30 Days
110+
111+
- improve weekly review so it feels worth paying for
112+
- validate restore purchases and billing flows
113+
- instrument funnel metrics: snapshot completion, paywall views, trial starts, conversion
114+
115+
### Final 30 Days
116+
117+
- run TestFlight with real watch users
118+
- tune onboarding based on permission drop-off
119+
- ship only after retention on the morning-check habit is real
120+
121+
## Success Metrics
122+
123+
- install to first snapshot completion
124+
- HealthKit grant rate
125+
- percentage of users opening the app at least 4 mornings per week
126+
- weekly review open rate
127+
- paywall view to trial start
128+
- trial to paid conversion
129+
130+
## Rule For Future Features
131+
132+
Every new feature should answer one question:
133+
134+
- does this make the daily decision clearer, more trusted, or more actionable?
135+
136+
If not, it is probably not a priority yet.

apps/HeartCoach/Legal/privacy-policy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ When you sign in with Apple, we receive an anonymous, app-specific identifier is
3636

3737
### 1.3 Subscription Information
3838

39-
Thump is free for the first year with full access to all features. No payment information is collected during this period. If you choose to subscribe after the free period, Apple processes your payment. We only receive confirmation of your subscription tier and its status. We do not have access to your payment method, credit card number, or billing address.
39+
Some users may have grandfathered launch access for a limited period. If you choose to subscribe, Apple processes your payment. We only receive confirmation of your subscription tier and its status. We do not have access to your payment method, credit card number, or billing address.
4040

4141
### 1.4 Usage Analytics (Opt-In)
4242

apps/HeartCoach/Legal/terms-of-service.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ Thump uses Sign in with Apple for authentication. You are responsible for mainta
3838

3939
---
4040

41-
## 5. Launch Offer and Subscriptions
41+
## 5. Launch Access and Subscriptions
4242

43-
### 5.1 First-Year Free Access
43+
### 5.1 Grandfathered Launch Access
4444

45-
All users who download Thump during the launch period receive **complimentary full access to all features for one (1) year** from the date of their first sign-in. No subscription or payment is required during this period.
45+
Some users who joined Thump during the original launch period may retain **complimentary Coach access for one (1) year** from the date their launch access began. This launch access is grandfathered and is not guaranteed for all new users.
4646

47-
This includes access to all Pro and Coach tier features at no cost. You will be notified before the free period ends and given the option to subscribe to continue using premium features.
47+
If you have grandfathered launch access, you will be notified before it ends and given the option to subscribe to continue using premium features.
4848

4949
### 5.2 Future Subscriptions
5050

51-
After the one-year free period, Thump may offer paid subscription tiers with different feature access levels. Subscription details and pricing will be displayed within the app before any charges apply. You will never be charged without your explicit consent.
51+
Thump may offer paid subscriptions, including the Coach plan currently presented in the app. Subscription details and pricing will be displayed within the app before any charges apply. You will never be charged without your explicit consent.
5252

5353
### 5.3 Billing
5454

apps/HeartCoach/PRODUCTION_TODO.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Thump Production TODO
2+
3+
## Pricing And Packaging
4+
5+
- [x] Reduce the public subscription surface to `Free + Coach`.
6+
- [x] Set Coach pricing in code to `$2.99/month`.
7+
- [x] Set Coach annual pricing in code to `$17.99/year` to land at about 50% off monthly billing.
8+
- [ ] Mirror the same price points in App Store Connect product configuration.
9+
- [ ] Remove or archive legacy `Pro` and `Family` products after legacy subscribers are migrated or retired.
10+
11+
## Monetization Enforcement
12+
13+
- [x] Stop auto-enrolling every new user into launch-year free access.
14+
- [x] Preserve grandfathered launch access for users who already have it.
15+
- [ ] Wire `SubscriptionTier` feature gates into the actual iOS views and flows.
16+
- [ ] Decide exactly which free features remain available without Coach.
17+
- [ ] Add paywall entry points at the feature boundaries, not only from Settings.
18+
19+
## Product Copy And Trust
20+
21+
- [x] Update Settings subscription copy so the app no longer says everything is free.
22+
- [x] Update the paywall to sell one Coach plan instead of three tiers.
23+
- [x] Update launch-access copy to make it clear that complimentary access is grandfathered.
24+
- [ ] Rewrite onboarding trust copy so it does not over-promise on-device-only behavior.
25+
- [ ] Align website marketing copy with the in-app promise and pricing.
26+
27+
## Legal And Compliance
28+
29+
- [x] Update markdown legal docs to stop promising a free first year to all new users.
30+
- [x] Update in-app legal text to reflect the single Coach offering.
31+
- [ ] Review website legal pages for any stale launch-offer or pricing language.
32+
- [ ] Re-check App Store privacy nutrition labels against current Firebase, telemetry, and bug-report behavior.
33+
34+
## Technical Readiness
35+
36+
- [ ] Create and test a StoreKit configuration file for local subscription testing.
37+
- [ ] Remove simulator-only Coach auto-grant before release builds are finalized.
38+
- [ ] Add end-to-end tests that verify free users hit the intended paywall gates.
39+
- [ ] Verify restore-purchase and cancellation UX on device and in TestFlight.
40+
41+
## Launch Readiness
42+
43+
- [ ] Update App Store screenshots and website pricing section to match `Free + Coach`.
44+
- [ ] Add conversion analytics for paywall view, trial start, purchase, restore, and cancellation.
45+
- [ ] Run TestFlight with real Apple Watch users before spending on acquisition.

apps/HeartCoach/Shared/Models/UserModels.swift

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public struct UserProfile: Codable, Equatable, Sendable {
220220
/// Email address from Sign in with Apple (optional, only provided on first sign-in).
221221
public var email: String?
222222

223-
/// Date when the launch free year started (first sign-in).
223+
/// Date when grandfathered launch access started.
224224
/// Nil if the user signed up after the launch promotion ends.
225225
public var launchFreeStartDate: Date?
226226

@@ -323,14 +323,14 @@ public struct UserProfile: Codable, Equatable, Sendable {
323323
steadyStreakDays >= 14
324324
}
325325

326-
/// Whether the user is currently within the launch free year.
326+
/// Whether the user is currently within the grandfathered launch year.
327327
public var isInLaunchFreeYear: Bool {
328328
guard let start = launchFreeStartDate else { return false }
329329
guard let expiryDate = Calendar.current.date(byAdding: .year, value: 1, to: start) else { return false }
330330
return Date() < expiryDate
331331
}
332332

333-
/// Days remaining in the launch free year. Returns 0 if expired or not enrolled.
333+
/// Days remaining in the grandfathered launch year. Returns 0 if expired or not enrolled.
334334
public var launchFreeDaysRemaining: Int {
335335
guard let start = launchFreeStartDate else { return 0 }
336336
guard let expiryDate = Calendar.current.date(byAdding: .year, value: 1, to: start) else { return 0 }
@@ -348,6 +348,9 @@ public enum SubscriptionTier: String, Codable, Equatable, Sendable, CaseIterable
348348
case coach
349349
case family
350350

351+
/// The only tier currently sold to new subscribers.
352+
public static let merchandisedTier: SubscriptionTier = .coach
353+
351354
/// User-facing tier name.
352355
public var displayName: String {
353356
switch self {
@@ -363,7 +366,7 @@ public enum SubscriptionTier: String, Codable, Equatable, Sendable, CaseIterable
363366
switch self {
364367
case .free: return 0.0
365368
case .pro: return 3.99
366-
case .coach: return 6.99
369+
case .coach: return 2.99
367370
case .family: return 0.0 // Family is annual-only
368371
}
369372
}
@@ -373,7 +376,7 @@ public enum SubscriptionTier: String, Codable, Equatable, Sendable, CaseIterable
373376
switch self {
374377
case .free: return 0.0
375378
case .pro: return 29.99
376-
case .coach: return 59.99
379+
case .coach: return 17.99
377380
case .family: return 79.99
378381
}
379382
}
@@ -398,7 +401,9 @@ public enum SubscriptionTier: String, Codable, Equatable, Sendable, CaseIterable
398401
]
399402
case .coach:
400403
return [
401-
"Everything in Pro",
404+
"Full wellness dashboard (HRV, Recovery, VO2, zone activity)",
405+
"Personalized daily suggestions and nudges",
406+
"Stress pattern awareness and anomaly alerts",
402407
"Weekly wellness review and gentle plan tweaks",
403408
"Multi-week trend exploration and progress snapshots",
404409
"Shareable PDF wellness summaries",
@@ -414,26 +419,42 @@ public enum SubscriptionTier: String, Codable, Equatable, Sendable, CaseIterable
414419
}
415420

416421
/// Whether this tier grants access to full metric dashboards.
417-
/// NOTE: All features are currently free for all users.
418422
public var canAccessFullMetrics: Bool {
419-
return true
423+
switch self {
424+
case .free:
425+
return false
426+
case .pro, .coach, .family:
427+
return true
428+
}
420429
}
421430

422431
/// Whether this tier grants access to personalized nudges.
423-
/// NOTE: All features are currently free for all users.
424432
public var canAccessNudges: Bool {
425-
return true
433+
switch self {
434+
case .free:
435+
return false
436+
case .pro, .coach, .family:
437+
return true
438+
}
426439
}
427440

428441
/// Whether this tier grants access to weekly reports and trend analysis.
429-
/// NOTE: All features are currently free for all users.
430442
public var canAccessReports: Bool {
431-
return true
443+
switch self {
444+
case .coach, .family:
445+
return true
446+
case .free, .pro:
447+
return false
448+
}
432449
}
433450

434451
/// Whether this tier grants access to activity-trend correlation analysis.
435-
/// NOTE: All features are currently free for all users.
436452
public var canAccessCorrelations: Bool {
437-
return true
453+
switch self {
454+
case .free:
455+
return false
456+
case .pro, .coach, .family:
457+
return true
458+
}
438459
}
439460
}

apps/HeartCoach/Tests/ClickableDataFlowTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,7 @@ final class SettingsOnboardingDataFlowTests: XCTestCase {
13941394
// MARK: - Profile: Launch Free Year
13951395

13961396
func testLaunchFreeYear_showsCorrectPlan() {
1397-
// When isInLaunchFreeYear is true, subscription section shows "Coach (Free)"
1397+
// Grandfathered launch users should still report an active complimentary plan.
13981398
let isInFreeYear = localStore.profile.isInLaunchFreeYear
13991399
// Just verify the property is accessible and returns a boolean
14001400
XCTAssertTrue(isInFreeYear == true || isInFreeYear == false)

apps/HeartCoach/Tests/ConfigServiceTests.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,30 +78,30 @@ final class ConfigServiceTests: XCTestCase {
7878
)
7979
}
8080

81-
// MARK: - Test: All Tiers Can Access All Features (all features are free)
81+
// MARK: - Test: Tier-Based Feature Access
8282

83-
func testFreeTierCanAccessAllFeatures() {
84-
XCTAssertTrue(ConfigService.canAccessFullMetrics(tier: .free))
85-
XCTAssertTrue(ConfigService.canAccessNudges(tier: .free))
86-
XCTAssertTrue(ConfigService.canAccessReports(tier: .free))
87-
XCTAssertTrue(ConfigService.canAccessCorrelations(tier: .free))
83+
func testFreeTierCanAccessOnlyStarterFeatures() {
84+
XCTAssertFalse(ConfigService.canAccessFullMetrics(tier: .free))
85+
XCTAssertFalse(ConfigService.canAccessNudges(tier: .free))
86+
XCTAssertFalse(ConfigService.canAccessReports(tier: .free))
87+
XCTAssertFalse(ConfigService.canAccessCorrelations(tier: .free))
8888
}
8989

90-
func testProTierCanAccessAllFeatures() {
90+
func testProTierCanAccessCorePremiumFeatures() {
9191
XCTAssertTrue(ConfigService.canAccessFullMetrics(tier: .pro))
9292
XCTAssertTrue(ConfigService.canAccessNudges(tier: .pro))
93-
XCTAssertTrue(ConfigService.canAccessReports(tier: .pro))
93+
XCTAssertFalse(ConfigService.canAccessReports(tier: .pro))
9494
XCTAssertTrue(ConfigService.canAccessCorrelations(tier: .pro))
9595
}
9696

97-
func testCoachTierCanAccessAllFeatures() {
97+
func testCoachTierCanAccessAllPremiumFeatures() {
9898
XCTAssertTrue(ConfigService.canAccessFullMetrics(tier: .coach))
9999
XCTAssertTrue(ConfigService.canAccessNudges(tier: .coach))
100100
XCTAssertTrue(ConfigService.canAccessReports(tier: .coach))
101101
XCTAssertTrue(ConfigService.canAccessCorrelations(tier: .coach))
102102
}
103103

104-
func testFamilyTierCanAccessAllFeatures() {
104+
func testFamilyTierCanAccessAllPremiumFeatures() {
105105
XCTAssertTrue(ConfigService.canAccessFullMetrics(tier: .family))
106106
XCTAssertTrue(ConfigService.canAccessNudges(tier: .family))
107107
XCTAssertTrue(ConfigService.canAccessReports(tier: .family))

0 commit comments

Comments
 (0)