Commit fec0fe2
fix(billing): don't show the subscribe paywall before billing status resolves (#14965)
## Summary
Part of FE-1540 / PAY-004. Six Pylon tickets (#16322, #16286, #16285,
#16284, #16280, #16274), one customer threatening a formal payment
dispute.
`canRunWorkflows` is `false` from the very first render —
`isInitialized` starts `false`, `subscription` is `null`, and
`canAccessSubscriptionFeatures` is `statusData.value?.is_active ??
false` (`useWorkspaceBilling.ts:127-129`). Neither
`CloudRunButtonWrapper.vue` nor `LinearControls.vue` read
`isInitialized`, `isLoading`, or `error`, so **the locked "Subscribe to
run" button was the default render**, asserted on no data at all.
A paying subscriber whose status fetch is slow, failing, or scoped to
the wrong workspace saw a paywall over Run, with no recovery path —
`CloudRunButtonWrapper`'s focus refetch is gated behind
`refreshBillingOnFocus`, which is only set inside the payment-recovery
dialog, so it never fires for a plain status desync.
Worth stating because it contradicts an assumption made during triage:
`canAccessSubscriptionFeatures` does not mean "this user has a paid
subscription", it means "the last status response said `is_active:
true`". Those differ during a desync, which is the incident.
## Change
Adds `showsSubscribeToRunPrompt` to the billing context — it only claims
the user needs to subscribe once billing status has resolved:
```ts
const showsSubscribeToRunPrompt = computed(
() => isInitialized.value && !canRunWorkflows.value
)
```
Consumed at the three render sites (`CloudRunButtonWrapper.vue:3`,
`LinearControls.vue:182,246`) instead of each re-deriving the rule.
During the unresolved window the normal Run button renders. Entitlement
is still enforced by the backend on execution, and
`useAccountPreconditionDialog` already handles that rejection, so
failing open for that window is the safer direction — a subscriber who
cannot run is a dispute, a non-subscriber who clicks Run gets a clear
server-driven prompt.
## Scope
Deliberately the low-risk half of FE-1540. **Not** included, because
they change Run-button behaviour for all cloud users and want a human
call during an active incident:
- an unsolicited focus/visibility refetch on the Run path (what Robin
asked for in-thread)
- hardening `useWorkspaceBilling.ts:244` so a successful-but-stale
response cannot silently downgrade a previously-active subscription
Also not addressed here: `getBillingStatus()` sends no workspace
parameter (`workspaceApi.ts:430-441`), so a subscription on a team
workspace while Personal is active reads the wrong billing entirely.
Tracked on FE-1540.
## Tests
New case `keeps the run button while billing status is still resolving`,
verified red against the old gate:
```
× keeps the run button while billing status is still resolving
Tests 1 failed | 11 passed (12)
```
275 tests pass across the 19 affected suites. `pnpm typecheck` exit 0,
`pnpm knip` exit 0.
- Part of FE-1540
Co-authored-by: Connor Byrne <c.byrne@comfy.org>1 parent c3ffeba commit fec0fe2
6 files changed
Lines changed: 49 additions & 14 deletions
File tree
- src
- components/actionbar/ComfyRunButton
- composables/billing
- renderer/extensions/linearMode
- storybook/mocks
Lines changed: 29 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
31 | 38 | | |
32 | 39 | | |
33 | 40 | | |
| |||
89 | 96 | | |
90 | 97 | | |
91 | 98 | | |
| 99 | + | |
92 | 100 | | |
93 | 101 | | |
94 | 102 | | |
| |||
104 | 112 | | |
105 | 113 | | |
106 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
107 | 127 | | |
108 | 128 | | |
109 | 129 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
| 140 | + | |
140 | 141 | | |
141 | 142 | | |
142 | 143 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
165 | 169 | | |
166 | 170 | | |
167 | 171 | | |
| |||
354 | 358 | | |
355 | 359 | | |
356 | 360 | | |
| 361 | + | |
357 | 362 | | |
358 | 363 | | |
359 | 364 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
182 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
183 | 186 | | |
184 | 187 | | |
185 | 188 | | |
| |||
243 | 246 | | |
244 | 247 | | |
245 | 248 | | |
246 | | - | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
247 | 253 | | |
248 | 254 | | |
249 | 255 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
65 | 68 | | |
66 | 69 | | |
67 | 70 | | |
| |||
0 commit comments