Commit 0f001a6
fix: stop transient ineligibility from permanently completing the first-run tour (Comfy-Org#15173)
## Summary
`handleStartupOutcome` treated "not a tour candidate on this boot" as
"already onboarded", permanently writing `Comfy.TutorialCompleted` for
users who were only transiently ineligible.
## Changes
- **What**: `isFirstRunCandidate()` returns false for five reasons, only
one of which is stable. `handleStartupOutcome` marked the tutorial
completed for all five. `Comfy.TutorialCompleted` is persisted
server-side against the user record and is only ever set to `true`, so a
single ineligible boot removed onboarding from that account forever.
Replaced the boolean with a three-way decision: `complete` (mark +
template browser), `defer` (template browser only, flag untouched),
`getting-started`. `isFirstRunCandidate()` is now derived from it, so
`handleUrlWorkflow` is unchanged.
Classification:
| condition | decision | why |
| -------------------------- | -------- |
---------------------------------------------------------------------------------
|
| `!isCloud` | complete | build-time `__DISTRIBUTION__`; cannot change
for this deployment |
| `isNewUser() === false` | complete | a determined returning user does
not become new again |
| `!onboardingTourEnabled` | defer | server flag, defaults false before
`/features` resolves, and flips during rollout |
| `!isDesktopWidth` | defer | viewport |
| `!isSubscriptionEnabled()` | defer | reads `window.__CONFIG__`,
written by the same `/features` fetch |
| `isNewUser() === null` | defer | undetermined is not a determination |
Two reachable cases this fixes. Signing up on a phone and returning on a
laptop is ordinary behaviour, and today it costs the user onboarding
permanently — raised by @MaanilVerma on
Comfy-Org#15091. The broader
variant, found while verifying that one: every boot taken while
`onboarding_tour_enabled` is off marks _every_ new user completed, so
the entire cohort onboarded before the rollout can never see the tour
once it is turned on.
## Review Focus
`isSubscriptionEnabled()` is `isCloud &&
window.__CONFIG__?.subscription_required` — deployment config, not
per-user subscription state, so "permanent" is arguable. It is
classified transient because `window.__CONFIG__` is populated by the
async `/features` fetch in `refreshRemoteConfig`, which sets it to `{}`
on timeout (5s), on 401/403, and on any network error. A wedged features
endpoint would otherwise burn the tour for every user who booted during
the outage. The cost of being wrong is asymmetric: a wrong `defer`
reopens the template browser one extra time, a wrong `complete` is
unrecoverable.
The `url-intent` branch still marks completed, unchanged. That user got
a real first-run experience — their workflow loaded, and
`handleUrlWorkflow` offers the tour over it — so it is a genuine
completion, not an environmental miss.
Candidacy is still read once per startup: `decideFirstRun()` is called a
single time in `handleStartupOutcome`, so a later resize, flag refresh,
or subscription change cannot unmount the Getting Started screen
mid-interaction. Covered by the existing test.
Non-candidates still get `Comfy.BrowseTemplates` on both paths.
## Tests
Existing disqualifier table split into permanent and transient. Four new
transient cases assert the flag is not written, plus an end-to-end case
that runs two boots through a `setSetting` mock that actually persists:
narrow viewport, then desktop. All five fail on `main` (`expected
"vi.fn()" to not be called at all, but actually been called 1 times`,
and `expected false to be true` for the two-boot case).
Co-authored-by: Connor Byrne <c.byrne@comfy.org>1 parent 0bc18c4 commit 0f001a6
2 files changed
Lines changed: 78 additions & 12 deletions
Lines changed: 53 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
88 | 91 | | |
89 | 92 | | |
90 | 93 | | |
| |||
97 | 100 | | |
98 | 101 | | |
99 | 102 | | |
100 | | - | |
| 103 | + | |
101 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
102 | 109 | | |
103 | 110 | | |
104 | | - | |
105 | 111 | | |
106 | 112 | | |
107 | 113 | | |
108 | 114 | | |
109 | | - | |
| 115 | + | |
110 | 116 | | |
111 | 117 | | |
112 | 118 | | |
| |||
119 | 125 | | |
120 | 126 | | |
121 | 127 | | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
122 | 139 | | |
123 | 140 | | |
124 | 141 | | |
125 | 142 | | |
126 | 143 | | |
127 | 144 | | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
128 | 175 | | |
129 | 176 | | |
130 | 177 | | |
| |||
167 | 214 | | |
168 | 215 | | |
169 | 216 | | |
170 | | - | |
171 | | - | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
172 | 220 | | |
173 | 221 | | |
174 | 222 | | |
| |||
Lines changed: 25 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
30 | 51 | | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
| 52 | + | |
36 | 53 | | |
37 | 54 | | |
38 | 55 | | |
| |||
51 | 68 | | |
52 | 69 | | |
53 | 70 | | |
54 | | - | |
| 71 | + | |
| 72 | + | |
55 | 73 | | |
56 | 74 | | |
57 | 75 | | |
58 | 76 | | |
59 | | - | |
| 77 | + | |
60 | 78 | | |
61 | 79 | | |
62 | 80 | | |
| |||
0 commit comments