Summary
The desktop app's 3-day premium trial begins silently the moment a user signs in for the first time. There is no in-app "your trial just started" surface, and users routinely discover the trial only when the Trial Ended toast fires three days later.
Root cause
The trial is purely derived from Firebase Auth's account creation timestamp:
# backend/utils/subscription.py:202-214
user_record = firebase_auth.get_user(uid)
creation_ms = user_record.user_metadata.creation_timestamp
creation_seconds = int(creation_ms / 1000)
trial_ends_at = creation_seconds + TRIAL_LENGTH_SECONDS # 3 * 24 * 60 * 60
No trial_started_at field is persisted; there's no opt-in endpoint; the client never asks the user. The 3-day clock starts whenever Firebase created the account, which is the OAuth sign-in moment.
On the desktop side, AuthService.swift:336-340 and :458-462 kick off state.startTrialMetadataRefresh() + TrialBannerService.shared.start() immediately after sign-in. The banner service only fires at <24h, <1h, and expired thresholds, so the user's first signal that a trial existed is the warning at the 2-day mark — or the expiration toast on day 3 if they were inactive in that window.
Proposed fix
Make the trial opt-in:
-
Backend — persist subscription.trial_started_at: int | None. get_trial_metadata reads the stored value when present; reports available: true, started_at: null when not. New POST /v1/users/me/trial/start writes now(), idempotent (returns the existing metadata if already started). Backward-compat: existing users currently mid-trial (Firebase creation + 3d > now) get a lazy backfill so we don't yank active access; users whose derived trial already expired silently get a fresh opt-in (the original auto-start was the bug, not the intent).
-
Desktop — first-sign-in modal showing the trial offer (feature bullets + Start / Maybe later). Settings → Plan & Usage card adds a "Start your 3-day premium trial" button when available && started_at == null. The existing countdown / expired UI is unchanged once the trial is actually started.
Acceptance criteria
- New desktop signup → no trial clock starts until the user clicks Start.
- Skipping the modal leaves the trial available; the user can start it later from Settings.
- Users currently mid-trial at deploy time continue uninterrupted.
Trial Ended toast never fires for a user who never opted in.
Out of scope
- Mobile / web (trial is desktop-only today and stays that way).
- Reworking the post-trial paywall or pricing surfaces.
Summary
The desktop app's 3-day premium trial begins silently the moment a user signs in for the first time. There is no in-app "your trial just started" surface, and users routinely discover the trial only when the Trial Ended toast fires three days later.
Root cause
The trial is purely derived from Firebase Auth's account creation timestamp:
No
trial_started_atfield is persisted; there's no opt-in endpoint; the client never asks the user. The 3-day clock starts whenever Firebase created the account, which is the OAuth sign-in moment.On the desktop side,
AuthService.swift:336-340and:458-462kick offstate.startTrialMetadataRefresh()+TrialBannerService.shared.start()immediately after sign-in. The banner service only fires at<24h,<1h, andexpiredthresholds, so the user's first signal that a trial existed is the warning at the 2-day mark — or the expiration toast on day 3 if they were inactive in that window.Proposed fix
Make the trial opt-in:
Backend — persist
subscription.trial_started_at: int | None.get_trial_metadatareads the stored value when present; reportsavailable: true, started_at: nullwhen not. NewPOST /v1/users/me/trial/startwritesnow(), idempotent (returns the existing metadata if already started). Backward-compat: existing users currently mid-trial (Firebase creation + 3d > now) get a lazy backfill so we don't yank active access; users whose derived trial already expired silently get a fresh opt-in (the original auto-start was the bug, not the intent).Desktop — first-sign-in modal showing the trial offer (feature bullets + Start / Maybe later). Settings → Plan & Usage card adds a "Start your 3-day premium trial" button when
available && started_at == null. The existing countdown / expired UI is unchanged once the trial is actually started.Acceptance criteria
Trial Endedtoast never fires for a user who never opted in.Out of scope