Skip to content
Merged
Show file tree
Hide file tree
Changes from 128 commits
Commits
Show all changes
201 commits
Select commit Hold shift + click to select a range
9897739
feat: add product coachmark onboarding tours
pythongosssss Jun 23, 2026
cc74e1d
add assets step to app mode tour
pythongosssss Jun 24, 2026
75af043
- change dialog to use standard component
pythongosssss Jun 25, 2026
f80deb9
[automated] Apply ESLint and Oxfmt fixes
actions-user Jun 29, 2026
f642384
change dash stroke to solid
pythongosssss Jun 30, 2026
5e23f76
fix: address coachmark tour review feedback
pythongosssss Jun 30, 2026
c51f963
trim comments
pythongosssss Jun 30, 2026
e9729ca
drop blankCanvas tour
pythongosssss Jun 30, 2026
f973626
improve test coverage
pythongosssss Jun 30, 2026
5dbb560
refactor + tidy
pythongosssss Jun 30, 2026
88cd848
fix test
pythongosssss Jun 30, 2026
fdfa988
feat: make app-mode tour explicit-only and refine assets step
pythongosssss Jun 30, 2026
5118212
feat: auto-open app-mode tour for populated apps
pythongosssss Jun 30, 2026
07356e3
tidy
pythongosssss Jun 30, 2026
b65da23
refactor: slim coachmark target tracking and spotlight chrome
pythongosssss Jun 30, 2026
87d0a11
fix: close the coachmark landing on Escape explicitly
pythongosssss Jun 30, 2026
a818b7e
test: open the template browser in the coachmark drift guard
pythongosssss Jun 30, 2026
9b8dd27
refactor: gate coachmark tour listeners behind an active-step component
pythongosssss Jun 30, 2026
5383e23
Merge origin/main into pysssss/product-coachmarks
pythongosssss Jul 1, 2026
3f84d4f
test: cover forced tour start, button labels, and spotlight escape
pythongosssss Jul 1, 2026
64d10da
refactor: place coachmark cards with Floating UI
pythongosssss Jul 1, 2026
831813a
fix: stop coachmark spotlight polling once its target settles
pythongosssss Jul 1, 2026
ce8b107
fix: correct coachmark tour skip telemetry and one-shot forcing
pythongosssss Jul 1, 2026
2312b21
refactor: address coachmark tour review feedback
pythongosssss Jul 1, 2026
96c2ae1
feat: add landing image to the app-mode tour
pythongosssss Jul 1, 2026
549200a
fix: address coachmark tour stacking, telemetry, and fixture review n…
pythongosssss Jul 2, 2026
e97746f
refactor: address coachmark tour PR feedback
pythongosssss Jul 2, 2026
e7cdfc8
chore: ignore derived coachmark keys in the unused-i18n check
pythongosssss Jul 2, 2026
9bcfda8
refactor: reconcile v-coachmark from the element's own data-coach-id
pythongosssss Jul 2, 2026
76abe8e
Merge branch 'main' into pysssss/product-coachmarks
pythongosssss Jul 2, 2026
bb39a51
fix: narrow the coachmark i18n exemption to derived step keys
pythongosssss Jul 2, 2026
7630cc9
fix: use the generic Skip label on the coachmark tour landing
pythongosssss Jul 3, 2026
61756e9
fix missing modal backdrop
pythongosssss Jul 3, 2026
9a71b54
refactor: move the coachmark tour state machine into a Pinia store
pythongosssss Jul 6, 2026
ccb3cd4
fix: restore the coachmark landing's md max-width
pythongosssss Jul 6, 2026
9730850
fix: keep the coachmark tour alive when outputs clear
pythongosssss Jul 6, 2026
25ce1eb
Merge remote-tracking branch 'origin/main' into pysssss/product-coach…
pythongosssss Jul 6, 2026
f1f7c53
feat: rework coachmark assets step and add step-back navigation
pythongosssss Jul 6, 2026
7df585e
flush -> nextTick
pythongosssss Jul 6, 2026
3f5b71a
refactor: use reka FocusScope for coachmark focus trapping
pythongosssss Jul 7, 2026
cb1be63
decrease padding
pythongosssss Jul 8, 2026
66b9f32
update wording
pythongosssss Jul 8, 2026
dff76db
make button grow, match design
pythongosssss Jul 8, 2026
62806d5
Merge branch 'main' into pysssss/product-coachmarks
pythongosssss Jul 9, 2026
13fd072
Merge branch 'main' into pysssss/product-coachmarks
AustinMroz Jul 13, 2026
62bb4d4
Merge remote-tracking branch 'origin/main' into pysssss/product-coach…
MaanilVerma Jul 17, 2026
e9caa31
test(onboarding): fix the coachmark drift-guard's assets-tab selector
MaanilVerma Jul 17, 2026
fcd151d
feat(onboarding): accept virtual coachmark targets
MaanilVerma Jul 25, 2026
b8c7035
feat(onboarding): add step onEnter hooks and async tour resolvers
MaanilVerma Jul 25, 2026
daef85f
feat(onboarding): mask-based scrim for interactive steps
MaanilVerma Jul 25, 2026
44a383e
feat(first-run-tour): resolve canvas nodes as coachmark targets
MaanilVerma Jul 25, 2026
1389a48
refactor(first-run-tour): drop the unused canvas union rect
MaanilVerma Jul 26, 2026
76d612c
refactor(onboarding): spotlight one target, drop the unused extra mas…
MaanilVerma Jul 27, 2026
0f93f63
refactor(first-run-tour): map canvas targets with litegraph's own camera
MaanilVerma Jul 27, 2026
3f20384
refactor(persistence): return a startup outcome instead of routing on…
MaanilVerma Jul 25, 2026
35267ba
feat(first-run-tour): add the onboarding tour feature flag
MaanilVerma Jul 25, 2026
71dd832
fix(tab): merge a caller's class into TabList
MaanilVerma Jul 25, 2026
4a59f74
feat(first-run-tour): decide the first-run entry surface at startup
MaanilVerma Jul 25, 2026
a661971
feat(first-run-tour): add the Getting Started screen
MaanilVerma Jul 25, 2026
6e852a7
feat(first-run-tour): resolve template roles from pinned nodes
MaanilVerma Jul 26, 2026
69aa9aa
chore(first-run-tour): drop an unused Getting Started string
MaanilVerma Jul 25, 2026
3143aa9
feat(first-run-tour): build the step sequence from resolved roles
MaanilVerma Jul 26, 2026
7dff28b
test(first-run-tour): clear the teleported screen between tests
MaanilVerma Jul 27, 2026
386812b
test(first-run-tour): guard the role pins against template drift
MaanilVerma Jul 26, 2026
edf4363
fix(first-run-tour): centre the Getting Started screen
MaanilVerma Jul 27, 2026
db7ad16
test(first-run-tour): fail the drift guard on the templates the grid …
MaanilVerma Jul 27, 2026
ed0be4b
feat(onboarding): let higher layers register a tour's steps
MaanilVerma Jul 27, 2026
7b4ca9d
feat(onboarding): guide the card onto a moving canvas target
MaanilVerma Jul 27, 2026
bdceede
feat(first-run-tour): build the tour's steps and frame each node
MaanilVerma Jul 27, 2026
312d6ba
feat(first-run-tour): run the tour from a picked template
MaanilVerma Jul 27, 2026
b96f82b
test(first-run-tour): cover the tour end to end from Getting Started
MaanilVerma Jul 27, 2026
c7a4e2e
fix(first-run-tour): tell a landed run apart from one that never started
MaanilVerma Jul 27, 2026
3c7adef
fix(first-run-tour): spotlight only the step the tour is on
MaanilVerma Jul 27, 2026
587fe99
test(first-run-tour): drive the tour walk to a real run outcome
MaanilVerma Jul 27, 2026
9414aa7
test(first-run-tour): give the step builder the real camera to measur…
MaanilVerma Jul 27, 2026
15cd5e0
feat(first-run-tour): offer templates once the tour ends
MaanilVerma Jul 27, 2026
70fa73c
test(first-run-tour): cover the paywalled run reaching the nudge
MaanilVerma Jul 27, 2026
2b4516e
Merge main
AustinMroz Jul 27, 2026
613ab51
Merge branch 'pysssss/product-coachmarks' into feat/tour-engine-canva…
MaanilVerma Jul 27, 2026
e48f50b
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Jul 27, 2026
108d529
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 27, 2026
1ef3b25
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 27, 2026
92ae020
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 27, 2026
d221753
test(first-run-tour): let the drift guard tolerate a lagging backend
MaanilVerma Jul 27, 2026
890f946
Merge branch 'feat/tour-role-resolution' of https://github.com/Comfy-…
MaanilVerma Jul 27, 2026
ad4f154
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 27, 2026
d3e486f
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 27, 2026
c671e33
Merge main (again)
AustinMroz Jul 27, 2026
45b1e53
refactor(onboarding): follow the camera instead of polling the target
MaanilVerma Jul 28, 2026
1cfccec
refactor(onboarding): position the card from the target itself
MaanilVerma Jul 28, 2026
7bd2325
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Jul 28, 2026
95c7053
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 28, 2026
cba63ef
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 28, 2026
45036e0
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 28, 2026
7b0893d
refactor(first-run-tour): address review on the Getting Started screen
MaanilVerma Jul 28, 2026
dcf4580
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 28, 2026
a5c4b5d
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 28, 2026
bc612a7
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 28, 2026
9340261
fix(first-run-tour): finish the tutorial thumbnail rotation
MaanilVerma Jul 28, 2026
cb3d4df
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 28, 2026
bc09396
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 28, 2026
4447ede
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 28, 2026
5d353a7
fix(first-run-tour): guard a collapsed viewport and type the tour mocks
MaanilVerma Jul 28, 2026
2328111
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 28, 2026
9a5d2be
refactor(onboarding): target canvas nodes by selector, not camera maths
MaanilVerma Jul 29, 2026
87a840f
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Jul 29, 2026
eaec0f9
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 29, 2026
aa404d5
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 29, 2026
c17829d
fix(onboarding): arm the card's glide when its target starts moving
MaanilVerma Jul 29, 2026
3dbb3de
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 29, 2026
2716aa5
fix(onboarding): address review feedback on the first-run tour
MaanilVerma Jul 29, 2026
99d9842
test(onboarding): pin the card's glide to its motion-safe variant
MaanilVerma Jul 29, 2026
1439645
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 29, 2026
0157a13
refactor(onboarding): name the nudge's appearance state for what it is
MaanilVerma Jul 29, 2026
79804df
fix(test): stop the tour walk on Run, not at the step count
MaanilVerma Jul 29, 2026
fe4022d
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 29, 2026
f719108
fix(test): pass the step count into the run helper
MaanilVerma Jul 29, 2026
607d5e9
refactor(onboarding): hold tour progress in one state, not four refs
MaanilVerma Jul 30, 2026
4c2da0c
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Jul 30, 2026
b2ad9a8
fix(first-run-tour): mark the tutorial done for users who never see t…
MaanilVerma Jul 30, 2026
55fe4c8
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 30, 2026
58cdc15
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 30, 2026
1e83269
fix(onboarding): move the card with the camera, not after it
MaanilVerma Jul 30, 2026
01fbbf2
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 30, 2026
c504c6a
fix(onboarding): close the first-run tour gaps from the PR-4 review
MaanilVerma Jul 30, 2026
7d35d71
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 30, 2026
48358e6
fix(onboarding): end a registered tour when its context goes
MaanilVerma Jul 30, 2026
b7464d6
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 30, 2026
4defd24
refactor(onboarding): derive canvas coachmark targets from layout, no…
MaanilVerma Jul 31, 2026
69a9893
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Jul 31, 2026
eed29ee
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 31, 2026
b4095ed
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 31, 2026
e44a64a
fix(onboarding): close the round-3 gaps in the first-run tour
MaanilVerma Jul 31, 2026
7e2491b
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 31, 2026
d7379b0
refactor(onboarding): give the tour one transition table
MaanilVerma Jul 31, 2026
7b5610e
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Jul 31, 2026
11a8689
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 31, 2026
743088f
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 31, 2026
417999a
refactor(onboarding): one way to end a tour whose context is gone
MaanilVerma Jul 31, 2026
ad8a3e6
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 31, 2026
44957e8
style(onboarding): group the Loader import with the local ones
MaanilVerma Jul 31, 2026
1047e9c
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Jul 31, 2026
c96563b
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Jul 31, 2026
7ba1f4a
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Jul 31, 2026
2aa059f
fix(onboarding): release the canvas target's observers with the tour
MaanilVerma Aug 1, 2026
54650a1
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Aug 1, 2026
dc30081
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 1, 2026
9319617
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 1, 2026
fde0147
fix(first-run-tour): dispose canvas targets when the tour releases them
MaanilVerma Aug 1, 2026
3aa63f7
fix(first-run-tour): end a run whose socket never came back
MaanilVerma Aug 1, 2026
19c491d
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 1, 2026
a2d25ee
Merge remote-tracking branch 'origin/main' into feat/tour-engine-canv…
MaanilVerma Aug 1, 2026
733ed53
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Aug 1, 2026
314fbfd
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 1, 2026
9e2be58
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 1, 2026
7475c4b
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 1, 2026
686ac59
refactor(onboarding): drop comments the code already says
MaanilVerma Aug 1, 2026
2465570
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 1, 2026
cfde0dd
test(onboarding): pin that changing a step's copy moves nothing else
MaanilVerma Aug 1, 2026
768e8ff
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 1, 2026
7c1f832
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Aug 1, 2026
fb2b285
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 1, 2026
2fbbd0e
test(onboarding): pin that a step renaming itself moves nothing else
MaanilVerma Aug 1, 2026
e6626e4
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 1, 2026
2dbe668
fix(onboarding): identify the step attempt, not just the run
MaanilVerma Aug 2, 2026
d8cf085
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Aug 2, 2026
adc5cf0
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 2, 2026
9fad789
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 2, 2026
8be9e73
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 2, 2026
7a7a56e
fix: keep onboarding off restored work and out of a reload dead-end
MaanilVerma Aug 2, 2026
d5aa5fc
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 2, 2026
b8273e2
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 2, 2026
27482b7
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 2, 2026
3619fdf
fix: drop the startup blank canvas instead of skipping persistence
MaanilVerma Aug 2, 2026
1e0c8e1
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 3, 2026
4158b5f
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 3, 2026
7228603
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 3, 2026
6fd3f36
test(first-run-tour): close the role-pin coverage gaps
MaanilVerma Aug 3, 2026
fd0ba7b
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 3, 2026
fd34efc
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 3, 2026
c722180
fix(first-run-tour): offer fallback templates that actually exist
MaanilVerma Aug 3, 2026
85110b3
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 3, 2026
157f6d2
fix(first-run-tour): repin the replaced fallbacks and relax the drift…
MaanilVerma Aug 3, 2026
caddfd0
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 3, 2026
8e10410
test(first-run-tour): follow the repinned fallbacks
MaanilVerma Aug 3, 2026
e2c4dc2
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 3, 2026
bac312b
fix(onboarding): fall back through Skip to the card when a step has n…
MaanilVerma Aug 3, 2026
0b96cdf
Merge branch 'feat/tour-engine-canvas-targets' into feat/getting-star…
MaanilVerma Aug 3, 2026
8e6d25a
Merge branch 'feat/getting-started-screen' into feat/tour-role-resolu…
MaanilVerma Aug 3, 2026
73bd3ba
Merge branch 'feat/tour-role-resolution' into feat/first-run-tour
MaanilVerma Aug 3, 2026
2368864
fix(first-run-tour): leave the subscribe button its own paywall dialog
MaanilVerma Aug 3, 2026
e197a18
fix(onboarding): count not_started per user and name a crashed resolver
MaanilVerma Aug 3, 2026
ba15925
fix(first-run-tour): stop a tour that cannot start rejecting the click
MaanilVerma Aug 3, 2026
7c71e4b
test(onboarding): gate the guards a green suite was not holding
MaanilVerma Aug 3, 2026
5546176
Merge branch 'feat/first-run-tour' into feat/first-run-tour-nudge
MaanilVerma Aug 3, 2026
061045b
fix(telemetry): keep step_count required on the stages that count steps
MaanilVerma Aug 3, 2026
451757a
fix(first-run-tour): stop congratulating a user whose tour never ran
MaanilVerma Aug 3, 2026
e56500d
test(first-run-tour): assert the nudge waits out the upgrade dialog
MaanilVerma Aug 3, 2026
13cc1e7
fix(first-run-tour): fail a run the queue never accepted
christian-byrne Aug 3, 2026
a75fa92
Merge remote-tracking branch 'origin/feat/first-run-tour' into christ…
christian-byrne Aug 3, 2026
7370588
Merge remote-tracking branch 'origin/main' into christian/rebase-14144
christian-byrne Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { config as dotenvConfig } from 'dotenv'
import MCR from 'monocart-coverage-reports'

import { COVERAGE_OUTPUT_DIR } from '@e2e/coverageConfig'
import { TOURS, TOUR_SEEN_SETTING } from '@/platform/onboarding/onboardingTours'
import {
ENTRY_PATHS,
TOUR_SEEN_SETTING
} from '@/platform/onboarding/onboardingTours'
import { NodeBadgeMode } from '@/types/nodeSource'
import { ComfyActionbar } from '@e2e/fixtures/components/Actionbar'
import { ComfyTemplates } from '@e2e/fixtures/components/Templates'
Expand Down Expand Up @@ -544,7 +547,7 @@ export const comfyPageFixture = base.extend<{
// Set tutorial completed to true to avoid loading the tutorial workflow.
'Comfy.TutorialCompleted': true,
// An auto-opened tour's blocker would break unrelated tests.
[TOUR_SEEN_SETTING]: Object.keys(TOURS),
[TOUR_SEEN_SETTING]: [...ENTRY_PATHS],
'Comfy.Queue.MaxHistoryItems': 64,
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize,
// Disable toast warning about version compatibility, as they may or
Expand Down
73 changes: 73 additions & 0 deletions browser_tests/tests/firstRunTourRolePins.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect } from '@playwright/test'

import type {
ExportedSubgraph,
ISerialisedNode,
SerialisableGraph
} from '@/lib/litegraph/src/types/serialisation'
import { CURATED_TEMPLATE_IDS } from '@/renderer/extensions/firstRunTour/gettingStarted/tutorialCards'
import { TOUR_ROLE_PINS } from '@/renderer/extensions/firstRunTour/roles/tourRolePins'
import type {
RolePin,
RolePins
} from '@/renderer/extensions/firstRunTour/roles/tourRolePins'
import { templateApiFixture as test } from '@e2e/fixtures/templateApiFixture'

const baseUrl = process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188'

const pinnedTemplates = Object.entries(TOUR_ROLE_PINS)

function pinnedRoles(pins: RolePins): [string, RolePin][] {
const roles = { source: pins.source, prompt: pins.prompt, sink: pins.sink }
return Object.entries(roles).filter(
(entry): entry is [string, RolePin] => entry[1] !== undefined
)
}

/** Subgraph definitions nest, and so does the resolver's host-mapping. */
function nodesOf(
graph: SerialisableGraph | ExportedSubgraph
): ISerialisedNode[] {
return [
...(graph.nodes ?? []),
...(graph.definitions?.subgraphs ?? []).flatMap(nodesOf)
]
}

function findNode(workflow: SerialisableGraph, id: number) {
return nodesOf(workflow).find((node) => String(node.id) === String(id))
}

test.describe('first-run tour role pins', { tag: '@workflow' }, () => {
test('every pinned node still exists with its pinned type', async ({
request
}) => {
const unserved: string[] = []

for (const [templateId, pins] of pinnedTemplates) {
const url = new URL(`/templates/${templateId}.json`, baseUrl).toString()
const response = await request.get(url)
if (!response.ok()) {
unserved.push(templateId)
continue
}

const workflow = (await response.json()) as SerialisableGraph
for (const [role, pin] of pinnedRoles(pins)) {
expect(
findNode(workflow, pin.id)?.type,
`${templateId} pins its ${role} to node ${pin.id}, which this backend no longer serves as a ${pin.type}`
).toBe(pin.type)
}
}
if (unserved.length)
test.info().annotations.push({
type: 'unserved templates',
description: `pins unverified, not served by this backend: ${unserved.join(', ')}`
})
expect(
pinnedTemplates.length - unserved.length,
`the Getting Started grid needs ${CURATED_TEMPLATE_IDS.length} templates and this backend serves too few of these pins — unserved: ${unserved.join(', ')}`
).toBeGreaterThanOrEqual(CURATED_TEMPLATE_IDS.length)
})
})
265 changes: 265 additions & 0 deletions browser_tests/tests/gettingStartedTour.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import { expect, mergeTests } from '@playwright/test'
import type { Locator, Page } from '@playwright/test'

import enMessages from '@/locales/en/main.json' with { type: 'json' }
import { TOUR_ROLE_PINS } from '@/renderer/extensions/firstRunTour/roles/tourRolePins'
import type { SupportedTemplateId } from '@/renderer/extensions/firstRunTour/roles/tourRolePins'

import type { PromptResponse } from '@comfyorg/ingest-types'

import type { AssetResponse } from '@/platform/assets/schemas/assetSchema'
import type { CloudSubscriptionStatusResponse } from '@/platform/cloud/subscription/composables/useSubscription'
import type { RemoteConfig } from '@/platform/remoteConfig/types'

import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import { ExecutionHelper } from '@e2e/fixtures/helpers/ExecutionHelper'
import { mockBilling } from '@e2e/fixtures/utils/cloudBillingMocks'
import { jsonRoute } from '@e2e/fixtures/utils/jsonRoute'
import { webSocketFixture } from '@e2e/fixtures/ws'

const wstest = mergeTests(test, webSocketFixture)

const { firstRun } = enMessages.onboardingCoachmarks
const GETTING_STARTED_TITLE = enMessages.gettingStarted.title
const RUN_STEP_TITLE = firstRun.run.title
const GENERATING_TITLE = firstRun.result.generating.title
const RESULT_IMAGE_TITLE = firstRun.result.image.title
const RESULT_FAILED_TITLE = firstRun.result.failed.title
const CARD_TESTID_PREFIX = 'getting-started-card-'

/** The prompt id the tour's run is queued under, so WS events can address it. */
const TOUR_JOB_ID = 'first-run-tour-prompt'

/** A prompt the queue accepts, so the walk does not depend on the backend's models. */
const QUEUED_PROMPT: PromptResponse = {
prompt_id: TOUR_JOB_ID,
number: 1,
node_errors: {}
}

const TOUR_FEATURE_FLAGS: RemoteConfig = {
onboarding_tour_enabled: true,
subscription_required: true
}

const ACTIVE_SUBSCRIPTION: CloudSubscriptionStatusResponse = {
is_active: true,
subscription_id: 'sub_first_run_tour',
renewal_date: '2099-01-01'
}

const NO_ASSETS: AssetResponse = {
assets: [],
total: 0,
has_more: false
}

function isPinned(id: string): id is SupportedTemplateId {
return Object.hasOwn(TOUR_ROLE_PINS, id)
}

/** How many steps the card says the tour has, once it says anything. */
async function tourLength(card: Locator): Promise<number> {
await expect(card).toContainText(/Step \d+ of \d+/)
const label = await card.textContent()
return Number(/Step \d+ of (\d+)/.exec(label ?? '')?.[1])
}

/**
* The grid backfills whichever curated templates a backend does not serve, so
* the walk tours a card that is actually on screen rather than a fixed id.
*/
async function firstPinnedTemplateOnScreen(
page: Page
): Promise<SupportedTemplateId> {
const testIds = await page
.locator(`[data-testid^="${CARD_TESTID_PREFIX}"]`)
.evaluateAll((cards) =>
cards.map((card) => card.getAttribute('data-testid') ?? '')
)
const templateId = testIds
.map((testId) => testId.slice(CARD_TESTID_PREFIX.length))
.find(isPinned)

expect(
templateId,
`this backend serves none of the pinned templates: ${testIds.join(', ')}`
).toBeDefined()
return templateId!
}

/**
* The walk the review asked for: a fresh user reaches Getting Started, picks a
* template, and the tour guides them through to a result.
*/
test.describe('First-run tour', { tag: ['@cloud', '@ui'] }, () => {
test.use({
initialSettings: {
'Comfy.TutorialCompleted': false,
'Comfy.OnboardingCoachmarks.Seen': ['appMode'],
'Comfy.VueNodes.Enabled': true
},
initialFeatureFlags: { onboarding_tour_enabled: true }
})

test.beforeEach(async ({ page }) => {
await page.route('**/api/features', (route) =>
route.fulfill(jsonRoute(TOUR_FEATURE_FLAGS))
)
await page.route('**/customers/cloud-subscription-status', (route) =>
route.fulfill(jsonRoute(ACTIVE_SUBSCRIPTION))
)
await page.route('**/api/assets**', (route) =>
route.fulfill(jsonRoute(NO_ASSETS))
)
})

/**
* The shared half of the walk: a fresh user reaches Getting Started, picks a
* pinned template, and the tour guides them to Run — where the funded and
* paywalled branches part. The run is queued under a known id, so whoever
* goes on to click Run can address it over the websocket.
*/
async function tourToRunStep(page: Page) {
const screen = page.getByRole('dialog', { name: GETTING_STARTED_TITLE })
const spotlight = page.getByTestId('coach-spotlight')
const card = page.getByTestId('coach-card')

await page.route('**/api/prompt', (route) =>
route.fulfill(jsonRoute(QUEUED_PROMPT))
)

await expect(screen).toBeVisible()

const templateId = await firstPinnedTemplateOnScreen(page)
await page.getByTestId(`${CARD_TESTID_PREFIX}${templateId}`).click()

await expect(screen).toBeHidden()
await expect(
spotlight,
'picking a template must spotlight the graph it loaded'
).toBeVisible()

const next = card.getByRole('button', { name: 'Next' })
const runTitle = card.getByText(RUN_STEP_TITLE)
const totalSteps = await tourLength(card)

for (let step = 1; step < totalSteps; step++) {
await expect(card).toContainText(`Step ${step} of ${totalSteps}`)
if (await runTitle.isVisible()) break
await next.click()
}

await expect(runTitle).toBeVisible()
await expect(
next,
'the Run step must offer no way forward except running'
).toBeHidden()

return { spotlight, card, totalSteps }
}

/** Clicks Run and waits for the Result step to admit the run is in flight. */
async function runFromTourStep(
page: Page,
card: Locator,
totalSteps: number
) {
await page.getByTestId('queue-button').click()

await expect(
card.getByText(GENERATING_TITLE),
'the run outlives its step, so the click moves the tour on and Result reports it'
).toBeVisible({ timeout: 15_000 })
await expect(
card,
'Run hands over to the last step, the one spotlighting where output lands'
).toContainText(`Step ${totalSteps} of ${totalSteps}`)
}

wstest(
'guides a first-run user from a template to a finished result',
async ({ comfyPage, getWebSocket }) => {
// A template load, three camera flights and a queued run do not fit the default budget.
test.slow()
const { page } = comfyPage
const execution = new ExecutionHelper(comfyPage, await getWebSocket())

const { card, totalSteps } = await tourToRunStep(page)
await runFromTourStep(page, card, totalSteps)

execution.executionStart(TOUR_JOB_ID)
execution.executionSuccess(TOUR_JOB_ID)

await expect(
card.getByText(RESULT_IMAGE_TITLE),
'the run finished, so the Result step has to stop saying it is still coming'
).toBeVisible({ timeout: 15_000 })
}
)

wstest(
'tells a first-run user when the run produced nothing',
async ({ comfyPage, getWebSocket }) => {
test.slow()
const { page } = comfyPage
const execution = new ExecutionHelper(comfyPage, await getWebSocket())

const { card, totalSteps } = await tourToRunStep(page)
await runFromTourStep(page, card, totalSteps)

execution.executionStart(TOUR_JOB_ID)
execution.executionError(TOUR_JOB_ID, '1', 'the run blew up')

await expect(
card.getByText(RESULT_FAILED_TITLE),
'announcing a result that does not exist is the bug D2 filed'
).toBeVisible({ timeout: 15_000 })
}
)

test.describe('without a subscription', () => {
test.beforeEach(async ({ page }) => {
await mockBilling(page)
})

test('leaves the nudge until the upgrade dialog closes', async ({
comfyPage
}) => {
test.slow()
const { page } = comfyPage
const { spotlight } = await tourToRunStep(page)
const nudge = page.getByTestId('first-run-nudge')
const upgradeDialog = page.getByTestId('dialog-overlay')

await page.getByTestId('subscribe-to-run-button').click()

await expect(upgradeDialog).toBeVisible()
await expect(
spotlight,
'a tour parked on a button that will never run has nowhere to go'
).toBeHidden()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never asserts the nudge is hidden while the dialog is open — only that the spotlight is hidden, and that the nudge is visible after Escape. Remove the dialogStack.length === 0 term from the watcher and this test still passes. One line here: await expect(nudge, 'the nudge sits below the modal stack').toBeHidden().


await page.keyboard.press('Escape')

await expect(upgradeDialog).toBeHidden()
await expect(
nudge,
'the tour ended, so the user still needs somewhere to go next'
).toBeVisible({ timeout: 10_000 })
})
})

test('starts no tour for a user who takes the blank canvas', async ({
comfyPage
}) => {
const { page } = comfyPage

await page.getByTestId('getting-started-blank').click()

await expect(
page.getByRole('dialog', { name: GETTING_STARTED_TITLE })
).toBeHidden()
await expect(page.getByTestId('coach-spotlight')).toBeHidden()
})
})
4 changes: 3 additions & 1 deletion src/components/actionbar/ComfyActionbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"
/>
<Suspense @resolve="comfyRunButtonResolved">
<ComfyRunButton />
<ComfyRunButton v-coachmark="FIRST_RUN_COACH_IDS.runButton" />
</Suspense>
<Button
v-tooltip.bottom="cancelJobTooltipConfig"
Expand Down Expand Up @@ -115,6 +115,8 @@ import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { FIRST_RUN_COACH_IDS } from '@/platform/onboarding/onboardingTours'
import { vCoachmark } from '@/platform/onboarding/vCoachmark'
import { useQueueStore } from '@/stores/queueStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
import { cn } from '@comfyorg/tailwind-utils'
Expand Down
4 changes: 3 additions & 1 deletion src/components/graph/GraphCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
import { useFirstRunEntry } from '@/renderer/extensions/firstRunTour/gettingStarted/firstRunEntry'
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
import { requestSlotLayoutSyncForAllNodes } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
Expand Down Expand Up @@ -559,9 +560,10 @@ onMounted(async () => {
)

// Restore saved workflow and workflow tabs state
await workflowPersistence.initializeWorkflow()
const startupOutcome = await workflowPersistence.initializeWorkflow()
await workflowPersistence.restoreWorkflowTabsState()
await workflowPersistence.loadTemplateFromUrlIfPresent()
await useFirstRunEntry().handleStartupOutcome(startupOutcome)
} finally {
workspaceStore.spinner = false
}
Expand Down
Loading
Loading