Skip to content

Paywall offer for mixer packs, shared packs, and the launch price stage (#705) - #770

Merged
Skeyelab merged 1 commit into
mainfrom
packs-offer-705
Oct 16, 2026
Merged

Skeyelab merged 1 commit into
mainfrom
packs-offer-705

Conversation

@Skeyelab

Copy link
Copy Markdown
Member

#705, the Kit for slices 3 and 4: the offer model, shared packs, and the price stage. Stacked on #769. Auto-merge stays off until it's retargeted to main. Still no app change; the app PR wires this up behind PackAvailability.

Decisions it implements (Eric, through the coordinating session, 2026-10-16)

  • Offer (PackOffer.items):
    • Pro owned: nothing.
    • Packs not offered in this build: Pro only.
    • A pack bought by this person: only .upgrade, since a second pack is hidden.
    • Only a Family Shared pack: .pro, which is option (a).
    • An Armed X Air or X32: .pack(family), then .pro.
    • Otherwise, including a generic input: .pro.
  • Headline (PackOffer.headline): "Pro for your XR18", from the linked Mixer's model. Without a link it uses the family's name ("X Air", "X32").
  • Availability: PackAvailability is Debug only. It doesn't reach TestFlight or the App Store until Eric tries packs on a device.
  • Price stage: PriceStage.current = .launch, with the label "Launch price" at that stage only. The coordinating session set Pro to $4.99 in App Store Connect. Moving to the next stage is a one-line PR.

Shared versus bought (OwnedPurchase.isFamilyShared, ProPurchases.boughtPacks, saved for cold start)

  • An upgrade this person bought counts as Pro only with a pack they bought. Buying a pack and the upgrade, then refunding the pack, can't lean on a relative's shared pack.
  • An upgrade shared with them counts with any owned pack. The upgrade has Family Sharing on like Pro (Eric), so a family keeps the buyer's Pro.
  • The app PR must: set isFamilyShared from transaction.ownershipType == .familyShared in ProStore.refreshOwned() (today it's always false), load the pack and upgrade products, and pass boughtPacks to PackOffer.items.

Tests, written first (PackOfferTests, ProPurchasesTests):

  • Red first, then green:
    • the offer suite (compile: no PackOffer or PackAvailability);
    • sharedPackGetsPro, launchPrice and boughtVersusShared (compile);
    • boughtUpgradeNeedsBoughtPack (behavioral: the old rule let a shared pack carry it);
    • the headline trims a trailing newline (behavioral).
  • Passed on their first run: sharedUpgradeWithSharedPack, because the old rule already allowed it; it now guards that case. Also the existing lastKnownPacks round-trip, which first went red when boughtPacks wasn't saved and was fixed by saving it.

Reviewed: a code-review agent found no blockers. Its "should the upgrade use bought packs?" question was settled by Eric's Family Sharing answer, as above. Its two nits (wording, newline trim) are done.

Checked: full package tests (1,031) pass, and VERIFY_QUICK=1 ./scripts/verify.sh passes.

…ge (#705)

PackOffer.items decides what the paywall offers: the Armed X Air or X32's pack
first with Pro beside it, Pro only for a generic input, only the upgrade for
someone who bought a pack (a second pack is hidden), full Pro for someone whose
only pack is Family Shared, nothing on Pro. The headline is "Pro for your
XR18". PackAvailability offers packs in Debug builds only. PriceStage.current
is .launch, labeled "Launch price".

OwnedPurchase.isFamilyShared and ProPurchases.boughtPacks tell bought packs
from shared ones (saved for cold start). An upgrade bought by this person needs
a pack they bought; a shared upgrade rests on any owned pack, so a family keeps
Pro and a refunded pack can't lean on a relative's.

Claude-Session: https://claude.ai/code/session_01K4ihr1i6m1uuUswAENvJKJ
Assisted-by: Claude Code:claude-opus-5-5
@Skeyelab
Skeyelab changed the base branch from packs-upgrade-705 to main October 16, 2026 05:01
@Skeyelab
Skeyelab enabled auto-merge (squash) October 16, 2026 05:01
@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.../ShowRecorderKit/Sources/Recording/PackOffer.swift 85.71% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

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