Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c867782
feat(billing): add dark versioned unified storefront contract
Git-on-my-level Oct 13, 2026
062434b
refactor(codegen): extract storefront and goals schema-group metadata
Git-on-my-level Oct 13, 2026
269820c
chore(mobile): record internal storefront wire contract
Git-on-my-level Oct 13, 2026
5f714cc
test(billing): reuse payment router for byte-identity cases
Git-on-my-level Oct 13, 2026
012d358
feat(billing): recommend storefront tier from opening context
Git-on-my-level Oct 13, 2026
1d2236b
chore(codegen): refresh storefront context projections
Git-on-my-level Oct 13, 2026
9c05a4f
fix(billing): block lower storefront offers for legacy subscribers
Git-on-my-level Oct 13, 2026
2d808d4
Merge origin/main into storefront contract lane
Git-on-my-level Oct 13, 2026
3e832ea
feat(billing): render the plans sheet as schema_version 2
Git-on-my-level Oct 13, 2026
db81339
Merge origin/main into storefront presentation v2
Git-on-my-level Oct 13, 2026
959c9d2
fix(billing): satisfy the storefront typecheck
Git-on-my-level Oct 13, 2026
aa8ba72
Merge origin/main into storefront presentation v2
Git-on-my-level Oct 13, 2026
5ab792d
test(mobile): decode nullable storefront tiers in the v2 golden
Git-on-my-level Oct 13, 2026
5b1ce00
fix(billing): allow the unpublished plans schema 1 to become schema 2
Git-on-my-level Oct 13, 2026
6e9dfc0
Merge origin/main into storefront presentation v2
Git-on-my-level Oct 13, 2026
17cd544
Restore Dart files the merge hook reformatted
Git-on-my-level Oct 13, 2026
4bd6a09
Merge origin/main into storefront presentation v2
Git-on-my-level Oct 13, 2026
2d51ee5
Restore files the merge hook reformatted
Git-on-my-level Oct 13, 2026
445c5f7
fix(billing): offer only the price a cancel or yearly switch can buy
Git-on-my-level Oct 13, 2026
a7eadb7
Merge origin/main into storefront presentation v2
Git-on-my-level Oct 13, 2026
06101b1
Merge remote-tracking branch 'origin/main' into lane/sub-w5-presentat…
Git-on-my-level Oct 13, 2026
cfd8b85
Merge commit '09772f9fad' into omi-monitor/pr-21397
Git-on-my-level Oct 13, 2026
71bff4f
Merge remote-tracking branch 'origin/main' into lane/sub-w5-presentat…
Git-on-my-level Oct 13, 2026
8c66931
Merge commit 'cfd8b85' into lane/sub-w5-presentation-v2
Git-on-my-level Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"kind":"none"}
216 changes: 108 additions & 108 deletions app/lib/backend/schema/gen/storefront_wire.g.dart

Large diffs are not rendered by default.

37 changes: 15 additions & 22 deletions app/test/backend/storefront_wire_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,22 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:omi/backend/schema/gen/storefront_wire.g.dart';

void main() {
Map<String, dynamic> fixture() =>
jsonDecode(File('../backend/tests/unit/fixtures/storefront/ladder_v1_free.json').readAsStringSync())
as Map<String, dynamic>;
test('storefront v2 golden decodes through the generated client', () {
final raw = File('../backend/tests/fixtures/storefront_v2/free_settings.json').readAsStringSync();
final decoded = GeneratedStorefrontResponse.fromJson(jsonDecode(raw) as Map<String, dynamic>);

test('generated DTO decodes the backend fixture without client plan rules', () {
final json = fixture();
final sheet = GeneratedStorefrontResponse.fromJson(json);
final tiers = sheet.tiers!;
expect(sheet.schemaVersion, 1);
expect(sheet.nextOfferTierId, 'plus:ladder_v1');
expect(tiers.map((tier) => tier.displayName), ['Free', 'Plus', 'Pro', 'Architect']);
expect(tiers[1].prices[1].annualSavings!.amountMinor, 3998);
expect(tiers[1].prices[1].monthlyEquivalent, r'$16.66/mo');
expect(GeneratedStorefrontResponse.fromJson(sheet.toJson()).toJson(), sheet.toJson());
});
expect(decoded.schemaVersion, 2);
expect(decoded.header, 'Choose your plan');
final tiers = decoded.tiers!;
expect(tiers.map((tier) => tier.name), ['Plus', 'Free', 'Pro', 'Architect']);
expect(tiers.first.recommended, isTrue);
expect(decoded.yearlyToggleLabel, 'Yearly · 2 months free');
final free = tiers.firstWhere((tier) => tier.name == 'Free');
expect(free.compact, isTrue);
expect(free.blockedLine, isNull);
expect(free.ctaLabel, isNull);

test('wire tier identity tolerates a future backend plan and additive fields', () {
final json = fixture();
final tiers = json['tiers'] as List<dynamic>;
final first = tiers.first as Map<String, dynamic>;
first['plan_id'] = 'future_backend_plan';
first['future_field'] = true;
final sheet = GeneratedStorefrontResponse.fromJson(json);
expect(sheet.tiers!.first.planId, 'future_backend_plan');
final roundTrip = GeneratedStorefrontResponse.fromJson(decoded.toJson());
expect(roundTrip.toJson(), decoded.toJson());
});
}
107 changes: 107 additions & 0 deletions backend/config/storefront_copy_v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"schema_version": 1,
"default_locale": "en",
"recording": {
"units_per_recorded_hour": 130,
"recording_share": 0.85,
"free_hours": 15,
"note": "units_per_recorded_hour is provisional (~$0.13 fully loaded: transcription $1.83 per 1,000 wall-clock minutes + ~19% extraction) from evidence/2026-10-12-unit-economics-and-usage.md §1/§3. Replace with shadow-ledger p50 (#21369)."
},
"budget_units_note": "Plan-catalog usage budgets in units per month (Plus 16360, Pro 44090, Architect 189463). plan_catalog.json has no usage-units allocation; this rate card is what the hours formula reads until the catalog exposes one. Free hours are fixed.",
"budget_units": {
"plus": 16360,
"operator": 44090,
"architect": 189463
},
"plan_names": {
"unlimited": "Neo",
"unlimited_v2": "Unlimited"
},
"locales": {
"en": {
"headers": {
"default": "Choose your plan",
"budget_80": "You've used 80% of this month's usage",
"budget_100": "You've used all of this month's usage",
"proactive_agent": "Reminders and follow-ups come with Pro",
"screen_context": "Screen help comes with Pro",
"mcp_daily_cap": "You've used today's {n} MCP requests",
"legacy": "Your plan",
"cancel_scheduled": "Your {plan} plan ends {date}"
},
"current": {
"free": "You're on Free",
"paid": "You're on {plan} · {price}/{period} · renews {date}",
"cancel": "You're on {plan} · {price}/{period}",
"cancel_ends": "You're on {plan} · ends {date}",
"paid_no_date": "You're on {plan} · {price}/{period}",
"name_only": "You're on {plan}",
"legacy_note": "We don't sell this plan anymore, but yours stays exactly as it is."
},
"hours_lines": {
"basic": "About {hours} hours of recording a month",
"plus": "About {hours} hours of recording a month, around {per_day} hours a day",
"operator": "About {hours} hours of recording a month, enough to wear it all day",
"architect": "About {hours} hours of recording a month, for all-day use on more than one device"
},
"per_day": {
"under_6_one": "about {n} hour a day",
"under_6": "about {n} hours a day",
"all_day": "enough to wear it all day",
"multi_device": "for wearing it all day, every day, on more than one device"
},
"bullets": {
"basic": [
"Transcripts, summaries and memories of your conversations",
"Ask Omi anything about your day",
"100 MCP requests a day"
],
"plus": [
"Everything in Free, with much more room",
"Use Omi on your Mac as well as your phone",
"500 MCP requests a day"
],
"operator": [
"Omi reminds you of things you said you'd do",
"On your Mac, Omi can see what you're working on and help with it",
"5,000 MCP requests a day"
],
"architect": [
"Everything in Pro, with over 4 times the usage",
"At least 5,000 MCP requests a day"
]
},
"bullet_focus": {
"mcp_marker": "MCP requests",
"proactive_agent": "Omi reminds you of things you said you'd do",
"screen_context": "On your Mac, Omi can see what you're working on and help with it"
},
"cta": {
"get_plus": "Get Plus",
"get_pro": "Get Pro",
"get_architect": "Get Architect",
"upgrade_pro": "Upgrade to Pro",
"upgrade_architect": "Upgrade to Architect",
"resubscribe": "Resubscribe",
"switch_yearly": "Switch to yearly · save {saving}"
},
"blocked_line": "Resubscribe first to change plans",
"prices": {
"headline_month": "{amount}/month",
"headline_year": "{amount}/month",
"subline_year": "{amount} billed yearly · {free}",
"subline_year_no_free": "{amount} billed yearly",
"months_free_one": "1 month free",
"months_free_many": "{n} months free",
"renewal_month": "Renews monthly until you cancel. Cancel anytime in settings.",
"renewal_year": "Renews yearly until you cancel. Cancel anytime in settings.",
"toggle": "Yearly · {free}"
},
"meter": {
"line": "{percent}% used · about {hours} hours of recording left · {reset}",
"nudge_80": "You've used 80% of this month's usage · {reset}"
},
"pro_gates": ["proactive_agent", "screen_context"]
}
}
}
8 changes: 4 additions & 4 deletions backend/config/storefront_ladder.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"schema_version": 1,
"tiers": [
{"plan_id": "basic", "display_name": "Free", "tagline": "Start building your memory", "recommended": false, "mcp_daily_cap": 100},
{"plan_id": "plus", "display_name": "Plus", "tagline": "Your everyday memory companion", "recommended": true, "mcp_daily_cap": 500, "proposed_prices": {"month": 1999, "year": 19990}},
{"plan_id": "operator", "display_name": "Pro", "tagline": "Proactive help with screen context", "recommended": false, "mcp_daily_cap": 5000, "proposed_prices": {"month": 4999, "year": 49990}},
{"plan_id": "architect", "display_name": "Architect", "tagline": "For demanding workflows", "recommended": false, "mcp_daily_cap": 5000}
{"plan_id": "basic", "display_name": "Free", "mcp_daily_cap": 100},
{"plan_id": "plus", "display_name": "Plus", "mcp_daily_cap": 500, "proposed_prices": {"month": 1999, "year": 19990}},
{"plan_id": "operator", "display_name": "Pro", "mcp_daily_cap": 5000, "proposed_prices": {"month": 4999, "year": 49990}},
{"plan_id": "architect", "display_name": "Architect", "mcp_daily_cap": 5000}
],
"currency": "usd",
"entitlement_version": "ladder_v1"
Expand Down
Loading
Loading