Skip to content

Feat/app store release prep#3

Open
matija wants to merge 8 commits into
mainfrom
feat/app-store-release-prep
Open

Feat/app store release prep#3
matija wants to merge 8 commits into
mainfrom
feat/app-store-release-prep

Conversation

@matija

@matija matija commented May 19, 2026

Copy link
Copy Markdown
Owner

No description provided.

matija and others added 8 commits May 18, 2026 16:29
…days

compute_status_pure was silently suppressing the Commercial Banner for
14 days after commercial_detected_at was set. The grace_end gate served
no purpose — banner should appear as soon as commercial usage is detected,
subject only to the per-session banner_dismissed flag.

grace_period_ends on LicenseStatus is kept (always None) so the frontend
LicenseBadge popover continues to work unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prepare the build infrastructure for Mac App Store distribution as a
parallel target to the existing GitHub Releases / Homebrew (Direct)
build. Pure config + entitlements plumbing; no runtime behaviour change.

- Strip macOS-only fields from base tauri.conf.json (titleBarStyle,
  hiddenTitle, windowEffects, macOSPrivateApi).
- Add tauri.macos.conf.json — Direct overlay, auto-merged on macOS.
- Add tauri.mas.conf.json — MAS overlay (passed via --config). Disables
  macOSPrivateApi, drops the sidebar windowEffect, restricts bundle to
  .app, points at the MAS signing identity / provisioning profile /
  entitlements, and turns off the updater plugin + updater artifacts.
- Add Esploro-MAS.entitlements (app-sandbox + network.client only).
- Add Cargo `mas` feature flag for upcoming P2 source-code gating.

Note: tauri-build reads `tauri`'s features list in Cargo.toml literally
and ignores feature aliases (tauri-apps/tauri#11142, #7940), so
`macos-private-api` stays literally listed to keep the default Direct
build working. Producing a MAS-compliant binary requires the release
pipeline to strip that feature from Cargo.toml before building, then
restore it — documented in Cargo.toml and PRD's Submission Flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
…eature flag

Introduces src-tauri/src/commands/iap.rs behind #[cfg(feature = "mas")] with
the four StoreKit command stubs (iap_get_products, iap_purchase, iap_restore,
iap_check_entitlement), wire types, and StoredEntitlement keychain helpers.

lib.rs now feature-gates the Dodo Payments commands, the tauri-plugin-updater
plugin, and the revalidate_license_background spawn behind not(feature = "mas").
The IAP command registrations are behind feature = "mas". This makes
cargo check --features mas succeed for the first time and prepares the surface
that the StoreKit integration (next step) will fill in.

No frontend changes; no behaviour change in the Direct build.

Co-authored-by: Cursor <cursoragent@cursor.com>
Complete the source-level split between the Direct and MAS license layers
started in the IAP scaffold. All Dodo Payments code paths in license.rs —
constants, StoredLicense + keychain helpers, call_dodo_validate / DodoError,
the dodo_*_message helpers, the revalidate_license_background task, and the
activate_license / deactivate_license / open_customer_portal commands — now
live behind #[cfg(not(feature = "mas"))], so the MAS binary stops compiling
them in.

Introduce a small CachedLicense enum as the cross-build abstraction for
"is there a valid cached commercial license?" and have compute_status_pure
take Option<CachedLicense> instead of a Dodo-shaped Option<&StoredLicense>.
Two cfg-gated translator helpers feed the same status state machine:
direct_cached_license maps StoredLicense by validated-at age (14-day offline
grace, fail-closed on unparseable timestamps), and mas_cached_license maps
the StoreKit StoredEntitlement by expires_at. The LicenseStatus wire shape
stays identical so the frontend builds against either flavour unchanged.

Tests reorganised into build-agnostic (5), Direct-only (7), and MAS-only
(4) groups. cargo clippy / cargo test pass on both flavours;
cargo check --features mas now has zero warnings (down from 12).

Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the four iap_* command stubs with a real StoreKit 1 implementation
in a new iap_storekit module behind --features mas. A custom Obj-C delegate
class (EsploroStoreKitDelegate) defined via objc2::define_class! conforms to
SKProductsRequestDelegate, SKRequestDelegate, and SKPaymentTransactionObserver,
and is registered as the default SKPaymentQueue observer at app launch.
Tauri commands bridge to it via tokio::sync::oneshot channels. Direct
(GitHub/Homebrew) build is unchanged; objc2 / objc2-foundation / objc2-store-kit
are target-gated to macOS and pulled in only when the mas feature is active.

Co-authored-by: Cursor <cursoragent@cursor.com>
The MAS frontend needed three things that the Direct UI didn't: a
StoreKit-driven purchase sheet, a Restore Purchases path, and a Manage
Subscription deep link to the Mac App Store. Rather than fork the Vite
bundle, the React tree now branches at runtime on a new `get_build_flavor`
Tauri command that returns "direct" or "mas" based on cfg!(feature = "mas").

- New `get_build_flavor` Tauri command (always-on, doc-commented as
  `staleTime: Infinity` material on the frontend) + two cfg-gated tests
  pinning the return value per build.
- New `PurchaseSheet` component renders the three IAP products fetched
  live, runs purchases through `iap_purchase`, and refreshes both
  LICENSE_STATUS_KEY and a new IAP_ENTITLEMENT_KEY cache on success.
- LicenseBanner now shows "Get a license" on MAS (opens PurchaseSheet)
  and keeps the existing "Purchase license" + "I have a license key"
  on Direct. The revalidation-required banner is gated behind !isMas
  since revalidation_required is always false on MAS.
- LicenseSettings has a MAS commercial panel that resolves the active
  product ID to a friendly plan label, shows renewal/purchase date,
  and exposes Manage subscription (subscriptions only) + Restore
  purchases. The MAS unlicensed panel offers Get a license + Restore.
  Direct UI is unchanged byte-for-byte.
- New `licenseApi` wrappers: getProducts, purchase, restore,
  checkEntitlement, getBuildFlavor, openManageSubscription (deep-links
  to macappstore://apps.apple.com/account/subscriptions).
- New types: BuildFlavor, IapProduct, IapPurchaseResult,
  IapPurchaseStatus, IapRestoreResult, IapEntitlement.

Verification: cargo clippy + test clean on Direct (22 passing) and
MAS (27 passing); npm run type-check and npm run lint clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wraps the Cargo.toml patch + cargo tauri build + restore sequence (per
PRD P1 note) in a single script so the developer can run `npm run
tauri:build:mas` instead of remembering the three-step dance. Trap on
EXIT guarantees Cargo.toml is restored even on Ctrl+C mid-build.

Co-authored-by: Cursor <cursoragent@cursor.com>
…scaffold)

Adds store-listing/ with the App Store-compliant 1024x1024 icon (no alpha,
no rounded corners — generator script lives alongside for reproducibility),
the full metadata block ready to paste into App Store Connect, and a
screenshots scaffold documenting the capture procedure for the remaining
manual step.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant