feat(platform): per-product seat-billing client + shared SeatPaywall#65
Merged
Conversation
Adds the consumer side of per-product seat billing so the five products
(gtm / creative / tax / legal / insurance) each adopt in ~2 lines.
platform/billing.ts:
- getProductEntitlement(http, key, productId, flag) — reads
GET /v1/billing/product-entitlement?product=<id> (user bearer), same
transport as readTangleTierState/getBalance. Fails OPEN (hasSeat:true)
on absent key, flag off, or unreachable endpoint so consumers never
break pre-rollout.
- isProductEntitled(ent) = hasSeat || onFreeTier — the one gate predicate.
- seatCheckoutUrl(baseUrl, productId) + http.seatCheckoutUrl(productId)
for the shared $100/mo seat, mirroring billingUrl().
- isSeatBillingEnabled() reads SEAT_BILLING_ENABLED, default OFF.
- ProductEntitlement / SeatStatus types; free tier gates on cumulative
lifetime spend (computed platform-side).
web-react/seat-paywall.tsx:
- SeatPaywall { product, onCheckout } — "Unlock <Product> — $100/mo,
includes $50/mo of AI usage". Allowance shown as a benefit; never the
ratio or the word margin (design §6.8). Tailwind tokens, inline SVG.
Version 0.16.0 -> 0.17.0 (additive). Flag default OFF, fail-open.
tangletools
approved these changes
Jun 15, 2026
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved PR — 20094331
Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-15T10:54:12Z
2009433 to
4127d28
Compare
tangletools
approved these changes
Jun 15, 2026
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved PR — 4127d281
Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-15T10:54:57Z
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The client half of per-product seat billing (per
docs/agent-shell-consolidationsibling specseat-billing-design.md). The platform half is a separate PR in agent-dev-container; both behindSEAT_BILLING_ENABLED(default OFF, fail-open) so nothing changes live until flipped.What it adds (agent-app, additive, v0.17.0)
/platform:getProductEntitlement(http, userApiKey, productId, flag)(GET/v1/billing/product-entitlement, user bearer, unwraps{success,data}, fails OPEN tohasSeat:truewhen the key/flag/endpoint is absent so consumers don't break pre-rollout);isProductEntitled(ent) = hasSeat || onFreeTier;seatCheckoutUrl(baseUrl, productId);isSeatBillingEnabled();FREE_TIER_SPEND_CAP_USD=2. TypesProductEntitlement/SeatStatus./web-react:SeatPaywallcomponent — "Unlock — $100/mo · includes $50/mo of AI usage" + checkout CTA. Allowance shown as a benefit, never the ratio/"margin" (design §6.8).Contract (matches the platform half)
GET /v1/billing/product-entitlement?product=<id>→{ success, data: { seatStatus, currentPeriodEnd, lifetimeSpentUsd, hasSeat, onFreeTier } }, user bearer.hasSeat/onFreeTiercomputed server-side (free tier =!hasSeat && lifetimeSpent < $2, no earmarking). Checkout at<baseUrl>/app/billing/seat/checkout?product=<id>(single $100 price +metadata.productId).~2-line product adoption
Verified
Rebased onto current main (v0.16.1 → 0.17.0).
pnpm buildclean; seat-billing tests 15/15. Merges clean.