Skip to content

[DO NOT MERGE] test(billing): FE-991 combined integration preview of Billing Rework V1 - #12792

Closed
dante01yoon wants to merge 226 commits into
mainfrom
jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces
Closed

[DO NOT MERGE] test(billing): FE-991 combined integration preview of Billing Rework V1#12792
dante01yoon wants to merge 226 commits into
mainfrom
jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces

Conversation

@dante01yoon

@dante01yoon dante01yoon commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

🚫 DO NOT MERGE. This is a throwaway integration / preview harness for FE-991 — it assembles every Billing Rework V1 branch onto the FE-904 facade base so the combined UI + API can be built and walked end-to-end in one preview / e2e environment. Each feature ships through its own PR; this branch is only for combined testing and is rebuilt as those branches change.

Why

Billing Rework V1 is split across two axes that converge at runtime — the UI (run button, credits tile, pricing, settings) consumes the API facade (billing status / balance / plans). They cannot be validated in isolation. This branch is the single point where both axes are combined behind the team_workspaces_enabled flag, so the full user flow can be exercised and missing/broken delegation boundaries surface before BE delivery.

How the two axes combine

              API axis (data substrate)                    UI axis (screens)
        ┌────────────────────────────────────┐    ┌──────────────────────────────────────┐
 base = │ #12622 facade · #12643 repoint      │    │ #12734 credits tile · #12759/#12761   │
  904   │ #12788 poller  · #12787 topup-refresh│ +  │ settings · #12763 misc UX · #12782    │
        │                                      │    │ roles · #12666 pricing(+#12644 slider)│
        │                                      │    │ #12786 run-lock · #12785 routing ·    │
        │                                      │    │ #12789 downgrade                      │
        └──────────────────┬───────────────────┘    └───────────────────┬──────────────────┘
                           └──────────── merged into this branch ────────┘
                            jaewon/fe-991-e2e-integration-...  (base = FE-904 facade)
                                              │
                                              ├─ NEXT: browser_tests/fixtures/data/billingMocks.ts
                                              │      (mockCloudBilling) = API axis mocked so the UI
                                              │      renders every state; mutation stubs cover the
                                              │      BE-gated flows (role PATCH · subscribe · removeMember)
                                              ▼
                          @cloud Playwright run (FE-991 scenario matrix) → walk every flow → gap report

Base is FE-904 (not main) because the UI axis (notably the credits tile) consumes the facade additions in #12622; the facade must be the substrate.

Test-target PRs included in this build

API axis (data substrate)

PR Ticket Adds
#12622 FE-904 B2 billing facade (resubscribe / topup / status fields) — base
#12643 FE-933 B3 repoint direct-bypass consumers onto the facade
#12788 FE-970 B8 unify the two BillingOp pollers (cancel-status → store)
#12787 FE-932 refresh billing status after a completed top-up (stale hasFunds)

UI axis (screens / flows)

PR Ticket Adds
#12734 FE-964 unified facade-driven Credits tile (DES-247)
#12761 FE-768 Settings ▸ Workspaces — Plan & Credits panel
#12759 FE-768 Settings ▸ Workspaces — Members invite UI
#12782 FE-770 promote / demote members (Change role) — draft, BE-gated
#12666 FE-934 unified pricing table + personal checkout (incl. #12644 FE-935 CreditSlider), Wire up team plan subscribe #12975
#12763 FE-769 workspace switcher popover left of profile + DES-246 copy
#12786 FE-978 role-aware run-lock for cancelled / inactive team plans
#12785 FE-878 route subscription / sign-in / credit preconditions to modal (routing-only)
#12789 FE-977 downgrade-to-personal member-removal confirm flow — draft, BE-gated (BE-1337)

All 12 composed; 0 skipped. One merge resolution (FE-932 vs FE-934 destructure in TopUpCreditsDialogContentWorkspace.vue, unioned) + the documented void startOperation('topup') fix once FE-932 + FE-970 both land.

Status

  • pnpm typecheck (vue-tsc): green, 0 errors. 113 files, +8574 / −2243 vs main.
  • Not yet on this branch (next commits): mockCloudBilling(page) helper + the new @cloud specs covering the FE-991 scenario matrix. The preview / preview-gpu build lets the combined flows be walked manually in the meantime (toggle team_workspaces_enabled).

Known limitation

Personal checkout confirm / success is not walkable even here — gated by the B1 code fork (useBillingContext.ts isInPersonalWorkspaceuseLegacyBilling plans = []), removed only in FE-966. Those two screens stay covered by Storybook (done in FE-934); this build covers the team path + render / gating / settings / members / switcher.

Relates FE-991. Per-feature review happens on the individual PRs above — do not review or merge this branch.


Refresh — 2026-06-12 (rebuilt 02ab2c126b..4b622ec998)

Re-synced this preview to the latest feature tips so the combined walk reflects current code:

One merge conflict resolved: src/storybook/mocks/useBillingContext.ts — the integration's BillingContext (extended by FE-964) requires billingStatus/subscriptionStatus/tier/renewalDate/resubscribe/topup + getMaxSeats; the mock now satisfies the full interface. Project type-check is green at HEAD.


Refresh — 2026-06-19 (B1 personal routing + billing history + remaining merges)

Added the remaining existing Billing Rework V1 PRs and re-synced the convergence levers so the combined walk reflects current code.

Newly included

PR Ticket Adds
#12953 FE-966 B1 single billing path — personal billing routes onto /api/billing/* (same as team) behind the new personal_workspace_billing_ready server flag. useBillingContext.ts type: isInPersonalWorkspace && !personalWorkspaceBillingReady → legacy, else workspace.
#12945 FE-967 B5 personal dialog UX — included but superseded by FE-934's unified pricing dialog (the unified table replaced the personal/team dialog fork); FE-966 carries the personal routing. FE-967's useLegacyBilling → workspaceApi delegation was dropped (redundant once FE-966 flips personal to useWorkspaceBilling).
#12955 FE-969 B7 billing history — usage history sources from GET /api/billing/events behind team_workspaces_enabled (retires the customerEventsService path).
#12951 team-subscribe robustness: useWorkspaceBilling.subscribe uses Promise.allSettled so a successful subscribe survives a failed post-write status/balance refresh (soft error, not reject).
#13001 FE-1104 pricing deep link?pricing=1 / ?pricing=team / ?pricing=personal opens the unified pricing table on app load (optionally on a preselected plan tab), original-owner gated via canManageSubscriptionLifecycle; member/promoted-owner is a silent no-op + param-strip. Stacked on FE-934 (#12666) + FE-770 (#12829) — both already present here. Post-Billing-V1 follow-up.

Re-synced tips: FE-934 #12666 (team Yearly/Monthly toggle + pricing-tab behavior), FE-878 #12785 (route the 402 queue paywall to the modal + subscriptionPaywallError.spec.ts).

FE-768 plan-credits tip (#12761) — now synced (was blocked on the cancel-permission model). The Billing V1 decision is confirmed (Slack thread + Notion "Slack Decisions & Open Questions" billing-permissions matrix): cancel and resubscribe are original-owner-only; other owners and members can leave the workspace. The merge reconciles the panel to that union — cancel/resubscribe gated by canManageSubscriptionLifecycle (FE-770), Leave-workspace menu item from #12761 (creator sees it disabled, others enabled), free-tier "10 min" runtime copy. Leave-disabled now reads the canonical store isCurrentUserOriginalOwner instead of the stale joinDate heuristic, so it agrees with the cancel gate. WorkspacePanelContent.vue kept at HEAD (FE-768 members/FE-769 superseded #12761's in-header invite/menu layout). Panel test reconciled to the creator-only model (14 pass). Note: #12761's own branch still carries the any-owner model + joinDate heuristic — fold the same reconciliation in when it rebases onto FE-770.

Still pending

  • FE-968 (B6 orientation banners): no PR exists yet.

pnpm typecheck (vue-tsc): green, 0 errors at HEAD.


Refresh — 2026-06-19 (re-synced feature tips: FE-768 · FE-969 · FE-934 · FE-1104)

Re-synced to the latest feature tips after the constituent PRs were updated. Each PR's own delta only was cherry-picked (not branch-merged), so main and other PRs' versions are not dragged onto this FE-904 base:

Also removed the in-app billing mock harness (billingMockHarness.ts) now that the real backend serves team-workspaces / billing data.

pnpm typecheck (vue-tsc): green, 0 errors at HEAD; affected unit tests green.


Refresh — 2026-06-20 (FE-965 #12954 inline team-invite block integrated)

PR Ticket Adds
#12954 FE-965 inline "Invite your team" block on the team-upgrade success step (DES-394) — now re-stacked on jaewon/fe-934-team-subscribe-wire.

Cherry-picked the single own commit (af8a7a1f41) on top of the FE-934 base already present here. It supersedes the earlier ad-hoc FE-965 carry (853c1164ba): normalizeEmail (trim + lowercase), seat cap = getMaxSeats - 1 (the buyer already holds one seat), the inviteMemberDialog.seatLimitReached i18n key, and the creatorMonthly team-success Storybook fixture.

Conflicts resolved (expected — not from FE-965's own code): the telemetry files conflicted on trackRunButton because af8a7a1's base carries the RunButtonProperties refactor (#12925, a main commit) that this FE-904 base intentionally lacks — kept HEAD's shape; the shared InviteMembersForm + invite i18n conflicted against FE-768's email-chips redesign — kept the chips UI, dropped the obsolete invite-link strings, added seatLimitReached.

pnpm typecheck (vue-tsc): green, 0 errors at HEAD; FE-965 + telemetry unit tests green (279).

dante01yoon and others added 30 commits June 3, 2026 20:58
…tatus fields

Add resubscribe(), topup(amountCents) and billingStatus/subscriptionStatus/
tier/renewalDate to the shared BillingContext so components stop bypassing
useBillingContext with raw workspaceApi calls (B2 of the billing-divergence
convergence plan, FE-904).

- types.ts: extend BillingActions + BillingState; export BillingStatus,
  BillingSubscriptionStatus, CreateTopupResponse from workspaceApi
- useWorkspaceBilling: real wiring (workspaceApi.resubscribe/createTopup,
  surface statusData fields)
- useLegacyBilling: legacy equivalents (resubscribe = fresh checkout;
  topup converts cents -> dollars via purchaseCredits; billing_status = null)
- useBillingContext: proxy the new members
- migrate orphaned callers (SubscriptionPanelContentWorkspace,
  TopUpCreditsDialogContentWorkspace, useSubscriptionCheckout) onto the facade

Facade standardizes topup on cents; the legacy adapter converts.
…ance fetch

The completed-topup balance refresh is already owned by the caller
(TopUpCreditsDialogContentWorkspace); refreshing inside the facade too caused
a redundant second fetch. Keep topup a thin pass-through that returns the
CreateTopupResponse so the caller orchestrates completed/pending.
Standalone presentational slider for the team-plan credit subscription. It
snaps to the 5 fixed DES-197 stops (200/400/700/1400/2500 USD) by driving the
shared reka-ui Slider in index space (min=0, max=4, step=1) — the user can
never land on a value in between. v-model carries the selected USD value; a
`change` event also emits { index, usd, credits }.

Thresholds live in a typed constant (teamPlanCreditStops.ts) hardcoded per
Figma DES-197 until the backend slider contract lands; a test guards that the
credit figures stay equal to usdToCredits(usd).

B4 standalone slice (FE-935). Not yet wired into PricingTableWorkspace
(deferred to FE-934, blocked on the BE slider contract).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lint-and-format failed on @intlify/vue-i18n/no-raw-text for the hardcoded
'/ month' and 'credits' template strings. Route both through vue-i18n
(subscription.perMonth, credits.credits — same key UserCredit.vue uses) and
inject an i18n instance in the component test. Also normalize tailwind class
order flagged by better-tailwindcss.
…s, yearly total)

Bring the team-plan slider up to the settled DES-197 / Slack design:
- discounted monthly price + struck pre-discount price + Save% badge
- '$N billed yearly' annual total
- stop labels now show CREDITS (42.2K..527.5K) with a credit icon and a
  gold highlight on the selected stop (was raw USD amounts)
- add threshold-based yearly discounts to TEAM_PLAN_CREDIT_STOPS
  (0/5/10/15/20%, $700=10% per design; others per the agreed sequence, TBD)
- USD / mo unit, drop the separate credits readout (credits live on the ticks)
The billing facade refactor moved resubscribe() onto useBillingContext,
but useSubscriptionCheckout's test still stubbed it on workspaceApi, which
the composable no longer calls. resubscribe was undefined at runtime, so
handleResubscribe threw "resubscribe is not a function" -- failing the
"emits close on success" and "shows error toast on failure" cases.

Wire mockResubscribe into the useBillingContext mock where the composable
reads it. The workspaceApi mock stays to shield the test from the real
@/scripts/api -> app.ts import chain.
…esEnabled

B4 (FE-934): a single pricing table for the Jun-5 model — one workspace with a
personal/team PLAN toggle (Gamma-style), per DES-197. New, flag-gated component
that will replace PricingTable.vue + PricingTableWorkspace.vue at cutover
(strangler).

- UnifiedPricingTable.vue: plan toggle, personal tier cards (facade plans with
  TIER_PRICING fallback), team column hosting CreditSlider, Enterprise card.
  Reuses useBillingContext; emits subscribe/resubscribe (personal) +
  subscribeTeam (team).
- SubscriptionRequiredDialogContentUnified.vue: host wiring the table to
  useSubscriptionCheckout for personal checkout; team checkout stubbed (toast)
  pending the BE slider contract (doc Open Q#2).
- showPricingTable: render the unified host when teamWorkspacesEnabled; the
  legacy PricingTable stays for the flag-off build.
- i18n: subscription.planScope / teamPlan / enterprise keys.

Stacked on the FE-935 CreditSlider branch. Personal checkout fully wired; team
checkout + live /api/billing/plans discount data deferred to the BE contract.
vue-tsc + oxlint clean; dispatcher tests pass (11).
Adds Personal / TeamPlan stories so the new table renders on the deployed
Storybook preview. initialPlanMode (default personal) lets the story show the
team view without the server flag (the toggle itself is flag-gated).
The real facade pulls in Firebase auth via the legacy adapter and crashes in
Storybook (TypeError: setPersistence). Alias it to a static stub so the new
table renders against TIER_PRICING / DES-197 fallbacks.
…imate count

- Save badge -> outlined primary pill, right-aligned (border-2 border-primary-background, text-primary-background, rounded-full, text-sm bold); bump price to 32px, per Figma 2983:29834
- Count the price up/down between stops via @vueuse useTransition (easeOutCubic, 350ms); honor prefers-reduced-motion
- Derive billed-yearly from the displayed monthly so it stays exactly 12x the shown price even mid-count
… when narrow

- The price (amount + struck + "USD / mo") is now an unbreakable unit (shrink-0 + whitespace-nowrap); the Save badge wraps to its own line on a narrow card instead of breaking "USD / mo" mid-word at the widest stop
- Story previews at the real DES-197 width: 512px card column with 32px padding -> 448px content
…opIndex)

- Accept `stops` + `defaultStopIndex` props (default to the hardcoded DES-197 set) so the slider can be fed from GET /api/billing/plans (BE-1254) without code changes; selectedIndex falls back to defaultStopIndex
- Add a prop-driven unit test and a "Backend-driven stops" story that maps the team_credit_stops API shape into the props
Guard selectedIndex so a shorter-than-expected backend `stops` array (or an
out-of-bounds `defaultStopIndex`) can't make `current` undefined and crash the
price computeds; floor `lastIndex` at 0 so the slider max is never negative.
`stops` is documented as required non-empty. Add a unit test asserting no
update:modelValue / change events fire when the slider is disabled.

Addresses CodeRabbit review on #12644 (undefined-`current` runtime path;
disabled-state test coverage).
- personal cards: replace uniform attribute matrix with progressive
  "What's included:" / "Everything in {prev}, plus:" bullets + credit block
  (monthly credits + "Generates ~N 5s videos*"); drop dead per-card popover
- unify monthly-credits / video-estimate / "Everything in {plan}" into shared
  subscription.* i18n keys reused by both personal and team
- team Details checks use foreground color, "Invite team members" copy
- billing-cycle badge "-20%" -> "Save 20%"
- Creator CTA to inverted (white) to match DES-197 and legacy PricingTable
- pricing dialog shell uses secondary-background (elevated) so the
  base-background cover panel + cards read as the darker well per DES-197;
  drop the translucent base/60 + backdrop-blur
- cover panel gets solid bg-base-background
- personal tier price unit "USD / mo / member" -> "USD / mo" (matches DES-197
  and the team slider; fixes awkward wrap on mobile)
The workspace adapter feeds subscription.renewalDate/endDate straight from
the API (ISO 8601), but the legacy adapter passed pre-formatted display
strings, so facade consumers like CreditsTile got different shapes per
adapter. Source both from subscriptionStatus and leave formatting to the
display site.
- useWorkspaceBilling: resubscribe refreshes status+balance and surfaces
  errors; topup stays a pass-through (no double-fetch) and surfaces errors
- useBillingContext: legacy topup converts cents to whole dollars
- types: trim convenience-accessor JSDoc, note whole-dollar-cents contract
Consolidate the divergent credits surfaces into a single CreditsTile that
always reads the useBillingContext facade, matching DES-247:

- New CreditsTile.vue: total + remaining, monthly/additional breakdown with
  a stacked progress bar, refresh, and permission-gated add-credits. Owns the
  post-checkout (focus / pending_topup) balance refresh.
- Extract the duplicated credits card out of SubscriptionPanelContentWorkspace
  and SubscriptionPanelContentLegacy onto the shared tile.
- Replace LegacyCreditsPanel (authStore.balance) with CreditsPanel.vue routed
  through the tile; repoint useSettingUI; delete LegacyCreditsPanel.
- creditsProgress helper + numeric getters on useSubscriptionCredits.
- i18n keys for the unified tile labels.
Drives a raw page with mocked Firebase auth + stubbed boot endpoints so the
cloud app initializes without a backend, then asserts the DES-247 tile renders
its total/progress/monthly+additional breakdown/add-credits in Settings ▸
Workspace ▸ Plan & Credits. Bypasses the comfyPage fixture (which requires the
OSS devtools backend).
Match the finalized DES-247 credits tile: single monthly-usage bar (gold,
fills with the consumed portion) with a Monthly / Refills header and
"{used} used" + "{remaining} left of {total}" labels, a divider, and a single
Additional-credits row with a credit icon and "Used after monthly runs out"
subtitle. Drops the two-segment bar and colored dots; updates i18n + the
unit/e2e assertions accordingly.
Container-query the tile: narrow widths drop the used/remaining labels
and breakdown subtitle, stack the additional-credits value, and compact
the monthly summary numbers (105K left of 200K). Align the additional
credits tooltip copy with the updated design.
…acade

The subscribe-clicked telemetry `current_tier` (SubscribeButton) and the
PostHog `subscription_tier` person property read the legacy useSubscription
store, which is empty/stale for team workspaces — so telemetry and analytics
record the wrong tier for team users today. Source the tier from
useBillingContext().tier (added in B2) so it is workspace-aware.

B3 of the billing convergence plan (FE-933). Stacked on FE-904 (B2).
useSubscriptionActions.handleRefresh and the SettingDialog credits-nav
refresh called the legacy authActions.fetchBalance (/customers only),
so team-workspace balances were never refreshed. Route both through
useBillingContext().fetchBalance, which is workspace-aware.

Part of B3 (FE-933).
The next-refresh line read formattedRenewalDate from the legacy
useSubscription store, which is empty for team workspaces, so the date
silently disappeared for team users (T5). Read the facade's raw ISO
renewalDate and format at the display site with the same en-US format.

Part of B3 (FE-933).
The personal-workspace avatar popover read tier/balance state from the
legacy useSubscription store and authStore directly. Source it all from
useBillingContext (tier, subscription, balance, isLoading, fetchStatus,
fetchBalance) and derive the tier badge via useWorkspaceTierLabel
instead of duplicating the tier-name mapping.

Part of B3 (FE-933).
Current-plan highlight and upgrade-vs-subscribe branching read the
legacy useSubscription store. Take isActiveSubscription/isFreeTier/tier
from useBillingContext and derive isYearlySubscription from the facade
subscription duration, matching PricingTableWorkspace. The billing
portal flow (accessBillingPortal deep-links + proration) stays as is —
facade manageSubscription is not behavior-identical.

Part of B3 (FE-933).
The cloud CI project runs a production build, which ignores the dev-only
ff: localStorage override — the Workspace settings panel never appeared
and the spec timed out on every run. Resolve the flag the way production
does: mock /api/features (the remote-config source) with
team_workspaces_enabled instead.
Route all same-origin /api, websocket, and bootstrap /api/features calls to https://pr-4393.testenvs.comfy.org so the labeled preview build exercises that ephemeral backend. Throwaway, scoped to this DO NOT MERGE integration PR.
@dante01yoon dante01yoon added the preview-cpu Creates a preview ephemeral environment for this PR (CPU only) label Jun 20, 2026
…E-934)

Members upsell 'Upgrade to Team' was re-wired to showSubscriptionDialog()
during the FE-768 members merge, dropping the FE-934 team-tab fix on this
integration branch. Restore showTeamPlans() -> show({ planMode: 'team' }).
A cancelled-but-still-active personal subscription renders both "Add credits" and "Resubscribe" in the credits row. With a wide (7-digit) balance the second button overflowed the fixed-width w-80 popover. Make the popover width fluid (w-fit, clamped min-w-80..max-w-96) so it stays 320px in the common single-action case and only grows (to ~370px) to fit both buttons.

Adds a @cloud e2e that asserts the Resubscribe button stays within the popover bounds.
…be (FE-934)

A Stripe-backed subscribe (needs_payment_method / pending_payment) only fired
the billing op poll; on completion billingOperationStore.handleSuccess closed
the pricing dialog and opened settings, so "You're all set" never showed. Team
subscribes were also gated out of the success step by a selectedTierKey-only
guard.

- await the billing op in the async checkout branches and advance to the
  success step instead of tearing the dialog down
- leave the subscription dialog open in handleSuccess (only top-ups close +
  surface settings); keep the success toast
- render the success step for team checkouts, sourcing the name, price and
  credits from the selected slider stop
- drive the team success invite block off the flat MAX_WORKSPACE_MEMBERS cap
- give the email input a persistent tertiary-background fill so it reads as a
  field at rest, not only on focus (Figma 3084-6116)
- add a showSubmit prop to InviteMembersForm and expose submit/canSubmit/loading
  so the success screen places "Send invites" in the footer next to Close,
  not glued under the input (Figma 3698-10316)
Port of the FE-768 Plan & Credits redesign onto the integration base,
using canManageSubscriptionLifecycle for the original-owner gate.

- ... menu per role: original owner = Edit / Cancel plan / Delete;
  promoted owner = Edit / Leave; member = Leave; personal = Edit, plus
  Cancel plan + Delete once on a paid plan.
- Cancel/Delete are creator-or-personal-owner only; the creator can't
  leave; Delete is locked while a team subscription is active.
- Render the menu via the design-system DropdownMenu (dividers, danger
  Delete) to match Figma; cancelled state shows Manage billing +
  Reactivate plan; personal Free shows Subscribe, both with the menu.
…reate/switch (FE-991)

A team plan can't be subscribed in place from a personal workspace, so a
personal user choosing a team plan in the unified pricing table is routed to
the Team Workspaces create/switch dialog instead of advancing into team
checkout. Gate on isInPersonalWorkspace; team workspaces keep the normal flow.
…ace (FE-991)

The new container gate test could fail on a cold-cache first run (onChooseTeam
observed 0 calls) due to a userEvent/happy-dom event-dispatch warm-up race; the
gate source is synchronous and correct. Wrap the positive assertions in
vi.waitFor so the emit lands deterministically.
A team plan's credit stop can't be changed, so moving the slider off the active
stop now disables the CTA with "Credits currently can't be changed" instead of
an enabled "Change plan" — for both active and cancelled subscriptions. The only
actionable subscribed state remains re-subscribing on the current stop. Drops the
now-unused teamPlan.changePlan key.
…icing table (FE-934)

Existing per-member (legacy) team subscribers (hyphenated team- plan slug, no team_credit_stop) keep the old tier-based team pricing table instead of the new unified credit-slider table. Detection lives in the billing facade (isLegacyTeamPlan); routing forks in showPricingTable before the unified branch. Restores modal:false for the legacy dialog's body-teleported popover and adds the missing success step to the resurrected legacy dialog.
…(FE-934)

Single classification of the active subscription (none/personal/new-team/
legacy-team) and the transitions it allows, so the pricing table consumes one
fact instead of re-deriving plan-vs-plan locks. new-team is keyed on the credit
stop (there is no 'team' SubscriptionTier); FREE and no-subscription collapse to
'none'; a cancelled-but-still-active plan keeps its type until billing ends.
…le (FE-934)

A new-team subscriber can't switch to a personal tier, and a team workspace on a
personal plan can't switch to team in place — both now disable the CTA with a
context-specific message driven by the facade subscriptionLock/planType. A
personal workspace keeps routing to create/switch a team workspace.
…934)

Yearly/monthly at the same credit stop is rejected by the BE
(TRANSITION_NOT_ALLOWED), so the off-cycle CTA now disables with
"Team plan currently can't be changed" instead of reading "Current plan".
…te (FE-934)

Carry the unified pricing-table rollback (no credit-stop / billing-cycle /
cross-plan locks, facade planType/subscriptionLock removed) onto the
team-subscribe stack, and let a personal workspace subscribe to a team plan
directly instead of rerouting to create/switch a workspace. The legacy team
table keeps its existing "choose team" flow.
Brings the single strip-before-await URL-cleanup refactor into the FE-991
integration branch so the deep link matches the FE-1104 PR for preview testing.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

preview preview-cpu Creates a preview ephemeral environment for this PR (CPU only) preview-gpu Creates a preview ephemeral environment for this PR (GPU available) size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants