Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
63b494a
feat(retention): add UID-held-out yearly switch placements (SCA-1808)
Git-on-my-level Nov 12, 2026
f44172a
test(retention): verify serving query and approval render contracts
Git-on-my-level Nov 12, 2026
84bf580
chore(retention): align kill-switch registry with default-on parser
Git-on-my-level Nov 12, 2026
dd41b13
SCA-1808: Keep unverifiable retention receipts out of live acceptance
Git-on-my-level Nov 13, 2026
9efe186
Merge main into SCA-1808 and repair retention verification checks
Git-on-my-level Nov 15, 2026
c48873b
SCA-1808: Bind refreshed render evidence to repaired source
Git-on-my-level Nov 15, 2026
32d7201
SCA-1808: Conform refreshed packet to UI evidence contract
Git-on-my-level Nov 15, 2026
8bb80d6
SCA-1808: Short-circuit retention admission reads
Git-on-my-level Nov 15, 2026
e178836
Merge main and re-arm Home retention admission on later presentation
Git-on-my-level Nov 15, 2026
8a54fdc
Use constant non-admission fixtures in Home regressions
Git-on-my-level Nov 15, 2026
c7d6cd6
Merge current main into SCA-1808 retention branch
Git-on-my-level Nov 15, 2026
452a9a8
Merge remote-tracking branch 'origin/main' into agent/m1-studio-codex…
Git-on-my-level Nov 15, 2026
2741779
fix(retention): close busy race and move UID erase to database
Git-on-my-level Nov 15, 2026
9400873
test(retention): bind Firestore witness to module-level imports
Git-on-my-level Nov 15, 2026
7c510ce
fix(SCA-1808): merge current main and upstream CI cleanup repair
Git-on-my-level Nov 15, 2026
dd66f42
fix(SCA-1808): refresh generated client method count after integration
Git-on-my-level Nov 15, 2026
26bdd57
fix(SCA-1808): refresh TypeScript method counts after integration
Git-on-my-level Nov 15, 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions .agent-artifacts/ui-evidence/yearly-retention-20261115/PACKET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
SCA-1808 candidate render supplement, November 15, 2026.

The ten synthetic iOS widget rasters are byte-identical to the 05:07Z packet
returned on SCA-1808. Upstream `cf2cb6b0ce` stacks yearly-switch fact values
below their labels above 130% text size. The normal-size isolated previews are
byte-identical before/after; the 150% previews show that layout difference.
Numbered rasters exercise the actual cancellation modal and the engaged
placement in a minimal Home host. Unnumbered previews isolate sheet content.

Nine tests passed at the repaired merge candidate. The execution command was:

```sh
cd app
flutter test --no-pub --reporter expanded \
--dart-define=YEARLY_RETENTION_RENDER_OUTPUT=../.agent-artifacts/sca-1808-repair/renders \
test/widgets/yearly_retention_test.dart \
test/widgets/sca1808_upstream_preview_test.dart
```

The supplemental fixture and source snapshots are in the issue attachment
`sca-1808-fresh-renders-20261115.zip`. No product code was changed to render it.
The original seven-image baseline and exact localized copy remain in the
adjacent `yearly-retention` packet. Yearly/cancellation English copy is unchanged.
Prices are synthetic fixture values. These are not device or deployed captures.

The owner authorized retaining this upstream accessibility layout for source
preparation; David has not approved this final packet. Approval/UI-author and
authorized rollout gates remain required. Live verification returns WAITING
(exit 2); no production reads/writes, activation, or measured lift is claimed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"version": 1,
"images": [
{
"file": "002-mobile-ios-cancel-control.png",
"platform": "mobile-ios",
"description": "Current cancellation control/no yearly offer; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "003-mobile-ios-cancel-after.png",
"platform": "mobile-ios",
"description": "Production cancellation, too_expensive treatment; yearly before discount; Cancel pinned; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "004-mobile-ios-engaged-before.png",
"platform": "mobile-ios",
"description": "Minimal Home host before/flag-off: no new placement; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "005-mobile-ios-engaged-after.png",
"platform": "mobile-ios",
"description": "Production engaged placement in minimal Home host, not full Home shell; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "006-mobile-ios-price-preview.png",
"platform": "mobile-ios",
"description": "Production existing yearly preview, exact synthetic charge and annual total; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "007-mobile-ios-payment-declined.png",
"platform": "mobile-ios",
"description": "Production switch decline, monthly retained; dismiss sheet to Cancel subscription; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "008-mobile-ios-head-preview-normal.png",
"platform": "mobile-ios",
"description": "Synthetic isolated yearly-switch preview; normal text; original PR head; not a device capture",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "009-mobile-ios-head-preview-large.png",
"platform": "mobile-ios",
"description": "Synthetic isolated yearly-switch preview; 150% text; original PR head; not a device capture",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "010-mobile-ios-merged-preview-normal.png",
"platform": "mobile-ios",
"description": "Synthetic isolated yearly-switch preview; normal text; local merged candidate; not a device capture",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "011-mobile-ios-merged-preview-large.png",
"platform": "mobile-ios",
"description": "Synthetic isolated yearly-switch preview; 150% text; local merged candidate; not a device capture",
"captured_by": "agent",
"source": "visual-audit"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"pr": 25158,
"merged_main": "9cc1565d383de4565b8bc492479b9d917731370b",
"upstream_layout_commit": "cf2cb6b0ce",
"state": "source repair candidate; approval and live outcomes unaccepted",
"text_scales": [
1.0,
1.5
],
"viewport": "430x932 logical px; pixelRatio 2; iOS widget variant",
"fixtures": "synthetic monthly subscription and preview; no production access",
"tests": "9 passed",
"normal_preview_identical": true,
"large_preview_identical": false,
"yearly_and_cancel_english_copy_changed": false,
"sha256": {
"004-mobile-ios-engaged-before.png": "c8a533f4e69db77bad22161f65d451011ab375083d1caa8dc28e67aaa7febe6d",
"002-mobile-ios-cancel-control.png": "18639044ae7e31c48f7b32b6e6785629e2c0343528946e2bfbdf98523843d5dc",
"011-mobile-ios-merged-preview-large.png": "26c6b7e1181505bbdbb7a4a7675ce68597d329ce4ac103b4e4073b144cfbd7a2",
"009-mobile-ios-head-preview-large.png": "8174c7bed0f6ca03287dee44a3985d6aac5bfed3686064b160548cfc639904fa",
"010-mobile-ios-merged-preview-normal.png": "4b6fdec8350f3c32746ae48c36c2a679a990a3aa943f3e09866596741654416b",
"008-mobile-ios-head-preview-normal.png": "4b6fdec8350f3c32746ae48c36c2a679a990a3aa943f3e09866596741654416b",
"007-mobile-ios-payment-declined.png": "5980c2d1eb80abe6c5b44dc8db88a3130a54ad895e88e8a61e814be4e83f189a",
"003-mobile-ios-cancel-after.png": "d87a81584b757d67c6e656853f6f34726843feb023419d0e39e3aa87190d4539",
"006-mobile-ios-price-preview.png": "023cfe9927cbf884783bf71f86598eba22faa1180ed4664db4b368e9d149de57",
"005-mobile-ios-engaged-after.png": "d4c3d860cd9a7d1562c6d7cb892bf3211f9f618fe770bcb00c30e69503e24154"
},
"base_pr_head": "dd41b1354cb9f38f0dcdb2e182bf960023dd8a1f",
"source_head": "9efe186c259ff93b556a75d8357bed63e5f97d03"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions .agent-artifacts/ui-evidence/yearly-retention/copy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"approval": "PENDING; no authorization or activation",
"locale": "en",
"strings": {
"yearlySwitchTitle": "Switch to Yearly",
"yearlySwitchHomeTitle": "Save {amount} a year with yearly",
"yearlySwitchHomeSubtitle": "Same plan, billed once a year",
"yearlySwitchPlanValue": "{plan}, {price} a year",
"yearlySwitchDueToday": "Due today",
"yearlySwitchRenews": "Renews",
"yearlySwitchCreditNote": "Includes credit for the rest of this month.",
"yearlySwitchDeclined": "Payment declined. You're still on monthly.",
"yearlySwitchFailed": "Couldn't switch. You're still on monthly.",
"yearlySwitchPreviewFailed": "Couldn't load your price.",
"yearlySwitchUnavailable": "Yearly billing isn't available for your plan right now.",
"yearlySwitchDoneTitle": "You're on yearly",
"yearlySwitchDoneBody": "{amount} charged today. Renews {date}.",
"cancelSubscription": "Cancel Subscription",
"retentionOfferBody": "Stay and pay {price} a month for the next {months} months.",
"retentionOfferAccept": "Stay for {percent}% off",
"cancel": "Cancel"
},
"fixture_amounts": {
"monthly_minor": 2999,
"yearly_minor": 26991,
"savings_minor": 8997,
"due_today_minor": 24492,
"currency": "USD"
},
"note": "Existing list-price yearly billing, not a new discount. Rendered amounts are synthetic, never verified live prices."
}
54 changes: 54 additions & 0 deletions .agent-artifacts/ui-evidence/yearly-retention/evidence.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"version": 1,
"images": [
{
"file": "001-mobile-ios-cancel-before.png",
"platform": "mobile-ios",
"description": "Historical production cancellation source at ac02feb05f0d6389f473e08a25980397b1348b0f; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "002-mobile-ios-cancel-control.png",
"platform": "mobile-ios",
"description": "Current cancellation control/no yearly offer; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "003-mobile-ios-cancel-after.png",
"platform": "mobile-ios",
"description": "Production cancellation, too_expensive treatment; yearly before discount; Cancel pinned; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "004-mobile-ios-engaged-before.png",
"platform": "mobile-ios",
"description": "Minimal Home host before/flag-off: no new placement; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "005-mobile-ios-engaged-after.png",
"platform": "mobile-ios",
"description": "Production engaged placement in minimal Home host, not full Home shell; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "006-mobile-ios-price-preview.png",
"platform": "mobile-ios",
"description": "Production existing yearly preview, exact synthetic charge and annual total; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
},
{
"file": "007-mobile-ios-payment-declined.png",
"platform": "mobile-ios",
"description": "Production switch decline, monthly retained; dismiss sheet to Cancel subscription; synthetic widget raster, not a device screenshot",
"captured_by": "agent",
"source": "visual-audit"
}
]
}
60 changes: 60 additions & 0 deletions .agent-artifacts/ui-evidence/yearly-retention/render-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"viewport_logical": [
430,
932
],
"pixel_ratio": 2,
"theme": "light",
"locale": "en",
"synthetic": true,
"approval": "PENDING",
"historical_source": "ac02feb05f0d6389f473e08a25980397b1348b0f",
"renders": [
{
"file": "001-mobile-ios-cancel-before.png",
"sha256": "18639044ae7e31c48f7b32b6e6785629e2c0343528946e2bfbdf98523843d5dc",
"role": "Historical production cancellation source at ac02feb05f0d6389f473e08a25980397b1348b0f"
},
{
"file": "002-mobile-ios-cancel-control.png",
"sha256": "18639044ae7e31c48f7b32b6e6785629e2c0343528946e2bfbdf98523843d5dc",
"role": "Current cancellation control/no yearly offer"
},
{
"file": "003-mobile-ios-cancel-after.png",
"sha256": "d87a81584b757d67c6e656853f6f34726843feb023419d0e39e3aa87190d4539",
"role": "Production cancellation, too_expensive treatment; yearly before discount; Cancel pinned"
},
{
"file": "004-mobile-ios-engaged-before.png",
"sha256": "c8a533f4e69db77bad22161f65d451011ab375083d1caa8dc28e67aaa7febe6d",
"role": "Minimal Home host before/flag-off: no new placement"
},
{
"file": "005-mobile-ios-engaged-after.png",
"sha256": "d4c3d860cd9a7d1562c6d7cb892bf3211f9f618fe770bcb00c30e69503e24154",
"role": "Production engaged placement in minimal Home host, not full Home shell"
},
{
"file": "006-mobile-ios-price-preview.png",
"sha256": "023cfe9927cbf884783bf71f86598eba22faa1180ed4664db4b368e9d149de57",
"role": "Production existing yearly preview, exact synthetic charge and annual total"
},
{
"file": "007-mobile-ios-payment-declined.png",
"sha256": "5980c2d1eb80abe6c5b44dc8db88a3130a54ad895e88e8a61e814be4e83f189a",
"role": "Production switch decline, monthly retained; dismiss sheet to Cancel subscription"
}
],
"limitations": [
"Widget rasters, not device/system-safe-area or full Home-shell acceptance.",
"No task-owned agent-flutter debug app was available; device/debugger verification not claimed.",
"No actual Stripe price, account, deployment, approval, activation or lift is represented."
],
"source_files_sha256": {
"app/lib/pages/settings/widgets/cancel_subscription_sheet.dart": "a049f3a5bda156086529967dc03c9ba6b437f0633b2d43328a4a8692aa00927f",
"app/lib/pages/settings/widgets/yearly_retention_placement.dart": "76a9256d3ac1a022c705d751aea568b1e78b3f6c8e464da618cbe8460f6133dc",
"app/lib/pages/settings/widgets/yearly_switch.dart": "a6490fdd2c338edaef0667ddfa84dc17a26b03a3c6d93463664e35eec3c9e4e0",
"app/test/widgets/yearly_retention_test.dart": "9a3edebfbccf1755e2168159910b50326d3243f8926e91aa3ca1764a29ca438c"
}
}
1 change: 1 addition & 0 deletions app/changelog/unreleased/20261112-yearly-retention.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"change":"Prepare the existing-price yearly switch before the cancellation discount and once for engaged monthly subscribers, behind approval, rollout, and stable holdout gates."}
23 changes: 23 additions & 0 deletions app/lib/backend/http/api/yearly_switch_retention.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Owns the optional yearly-retention admission/config transport.
// Preserve typed HTTP failures; only UI callers decide to hide an optional offer.
import 'dart:convert';

import 'package:omi/backend/http/api_result.dart';
import 'package:omi/backend/schema/gen/payments_wire.g.dart' as wire;
import 'package:omi/env/env.dart';

Future<ApiResult<wire.GeneratedYearlySwitchResponse>> claimYearlyRetention(String placement, {ApiSend? send}) {
if (!const {'cancel', 'engaged'}.contains(placement)) throw ArgumentError.value(placement, 'placement');
return executeApi(
request:
ApiRequest(url: '${Env.apiBaseUrl}v1/payments/yearly-switch-retention?placement=$placement', method: 'POST'),
send: send,
decode: (body) => wire.GeneratedYearlySwitchResponse.fromJson(jsonDecode(body) as Map<String, dynamic>),
);
}

Future<ApiResult<bool>> yearlyRetentionStatus({ApiSend? send}) => executeApi(
request: ApiRequest(url: '${Env.apiBaseUrl}v1/payments/yearly-switch-retention', method: 'GET'),
send: send,
decode: (body) => wire.GeneratedYearlySwitchResponse.fromJson(jsonDecode(body) as Map<String, dynamic>).eligible,
);
3 changes: 3 additions & 0 deletions app/lib/pages/home/page.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:omi/pages/settings/widgets/yearly_retention_placement.dart';

Check warning on line 1 in app/lib/pages/home/page.dart

View workflow job for this annotation

GitHub Actions / Hygiene

Large changed file

app/lib/pages/home/page.dart is 1191 lines; consider splitting files over 800 lines.
import 'package:omi/pages/settings/widgets/yearly_switch.dart';
// The home page: capture start/stop entry point, permission gating,
// foreground-service lifecycle, and on-device transcription wiring.
//
Expand Down Expand Up @@ -829,7 +831,7 @@
}

@override
Widget build(BuildContext context) {

Check warning on line 834 in app/lib/pages/home/page.dart

View workflow job for this annotation

GitHub Actions / Hygiene

Long function

Widget build(BuildContext context) is 161 lines; consider extracting focused helpers over 150 lines.
final content = MyUpgradeAlert(
upgrader: _upgrader,
dialogStyle: Platform.isIOS ? UpgradeDialogStyle.cupertino : UpgradeDialogStyle.material,
Expand Down Expand Up @@ -935,6 +937,7 @@
);
},
),
if (onHome) const YearlyRetentionPlacement(surface: YearlySwitchSurface.engaged),
// Home shows an active call in its capture row; Tasks gets the slim bar.
if (!onHome) const ActiveCallTopBar(),
Expanded(
Expand Down
Loading
Loading