mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-11 09:42:22 +00:00
## Summary
Aligns the workspace switcher and creation flow to DES-246 (FE-769): the
switcher popover now opens to the **left** of the profile menu instead
of on top of it, team workspace rows drop the tier badge, and the
create-workspace dialog matches the design's copy and surface.
## Changes
- **What**:
- `CurrentUserPopoverWorkspace.vue`: replace the nested PrimeVue
`Popover` (rendered on top of the menu) with an inline panel anchored
left of the selector row (`right-full`, top-aligned, outside-click close
via VueUse)
- `WorkspaceSwitcherPopover.vue`: tier badge only renders on the
personal workspace row ("Remove the tier badge for team workspaces,
since there'll only be one plan now")
- Copy (`en/main.json`): switcher create label "Create a team workspace"
("Explicitly say 'team'"); create dialog message "Workspaces keep your
projects and files organized. Subscribe to a Team plan to invite
members.", label "Workspace name", placeholder "Ex: Comfy Org"
- `CreateWorkspaceDialogContent.vue`: surface matched to the Create
Workspace / Default frame — 512px width, muted name label, filled 40px
TextInput (`bg-secondary-background`, `rounded-lg`, `px-4`)
- Invite-flow copy deltas from DES-246: none — #12759 (FE-768) already
matches the design verbatim
Was stacked on #12762 (FE-778); that PR merged, so this is now rebased
onto `main` with only the FE-769 commits.
- Fixes
[FE-769](https://linear.app/comfyorg/issue/FE-769/updates-to-misc-ux)
## Review Focus
- The switcher panel now lives inside the profile popover DOM (no
teleport): clicking a row keeps the menu open, outside-click closes only
the panel
- The CREATOR badge on the profile-menu workspace selector row (visible
in the Figma frame) is intentionally not included — it needs
`is_original_owner` from the BE role-change work and ships with FE-770
- `leave-last-workspace -> auto-create` flow is deferred (not V1),
intentionally untouched
## Screenshots (if applicable)
| Before (overlaps menu, tier badges, "Create new workspace") | After
(left of menu, no team badges, "Create a team workspace") |
| --- | --- |
| <img width="700" alt="before"
src="https://github.com/user-attachments/assets/5522fcca-91b5-49e6-beaa-df1b88bed018"
/> | <img width="1100" alt="after"
src="https://github.com/user-attachments/assets/ce74d42e-19bd-4fe6-9477-b22e5964736d"
/> |
Create-workspace dialog with DES-246 copy and surface (512px, filled
input):
<img width="900" alt="create dialog after"
src="https://github.com/user-attachments/assets/e78eff0a-1c0e-4bbb-ac70-6cc1da996682"
/>
141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import type { RemoteConfig } from '@/platform/remoteConfig/types'
|
|
import type { WorkspaceWithRole } from '@/platform/workspace/api/workspaceApi'
|
|
import type { WorkspaceTokenResponse } from '@/platform/workspace/stores/workspaceAuthStore'
|
|
import { comfyPageFixture } from '@e2e/fixtures/ComfyPage'
|
|
|
|
const PERSONAL_WORKSPACE_NAME = 'Personal Workspace'
|
|
const LONG_WORKSPACE_NAME =
|
|
'Quantum Renaissance Collective for Hyperdimensional Latent Diffusion Research and Experimental Workflow Engineering'
|
|
|
|
// text-sm rows render a single 20px line; a wrapped name is 40px+.
|
|
const SINGLE_LINE_MAX_HEIGHT_PX = 28
|
|
|
|
const mockRemoteConfig: RemoteConfig = { team_workspaces_enabled: true }
|
|
|
|
const mockListWorkspacesResponse: { workspaces: WorkspaceWithRole[] } = {
|
|
workspaces: [
|
|
{
|
|
id: 'ws-personal',
|
|
name: PERSONAL_WORKSPACE_NAME,
|
|
type: 'personal',
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
joined_at: '2026-01-01T00:00:00Z',
|
|
role: 'owner'
|
|
},
|
|
{
|
|
id: 'ws-team-long',
|
|
name: LONG_WORKSPACE_NAME,
|
|
type: 'team',
|
|
created_at: '2026-01-02T00:00:00Z',
|
|
joined_at: '2026-01-02T00:00:00Z',
|
|
role: 'member'
|
|
}
|
|
]
|
|
}
|
|
|
|
const mockTokenResponse: WorkspaceTokenResponse = {
|
|
token: 'mock-workspace-token',
|
|
expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
workspace: {
|
|
id: 'ws-personal',
|
|
name: PERSONAL_WORKSPACE_NAME,
|
|
type: 'personal'
|
|
},
|
|
role: 'owner',
|
|
permissions: []
|
|
}
|
|
|
|
const test = comfyPageFixture.extend({
|
|
page: async ({ page }, use) => {
|
|
await page.route('**/api/features', (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(mockRemoteConfig)
|
|
})
|
|
)
|
|
|
|
await page.route('**/api/workspaces', async (route) => {
|
|
if (route.request().method() !== 'GET') {
|
|
await route.fallback()
|
|
return
|
|
}
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(mockListWorkspacesResponse)
|
|
})
|
|
})
|
|
|
|
await page.route('**/api/auth/token', (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(mockTokenResponse)
|
|
})
|
|
)
|
|
|
|
await use(page)
|
|
}
|
|
})
|
|
|
|
test.describe('Workspace switcher', { tag: '@cloud' }, () => {
|
|
test('renders a long team workspace name on a single line', async ({
|
|
comfyPage
|
|
}) => {
|
|
const page = comfyPage.page
|
|
|
|
await comfyPage.toast.closeToasts()
|
|
await page.getByRole('button', { name: 'Current user' }).click()
|
|
await page.getByText(PERSONAL_WORKSPACE_NAME).click()
|
|
|
|
const longName = page.getByText(LONG_WORKSPACE_NAME)
|
|
await expect(longName).toBeVisible()
|
|
|
|
const box = await longName.boundingBox()
|
|
expect(box).not.toBeNull()
|
|
expect(box!.height).toBeLessThan(SINGLE_LINE_MAX_HEIGHT_PX)
|
|
})
|
|
|
|
test('opens the switcher to the left of the profile menu without overlap', async ({
|
|
comfyPage
|
|
}) => {
|
|
const page = comfyPage.page
|
|
|
|
await comfyPage.toast.closeToasts()
|
|
await page.getByRole('button', { name: 'Current user' }).click()
|
|
await page.getByTestId('workspace-switcher-trigger').click()
|
|
|
|
const panel = page.getByTestId('workspace-switcher-panel')
|
|
await expect(panel).toBeVisible()
|
|
|
|
const profileMenu = page.locator('.current-user-popover')
|
|
const panelBox = await panel.boundingBox()
|
|
const profileBox = await profileMenu.boundingBox()
|
|
expect(panelBox).not.toBeNull()
|
|
expect(profileBox).not.toBeNull()
|
|
expect(panelBox!.x + panelBox!.width).toBeLessThanOrEqual(profileBox!.x)
|
|
})
|
|
|
|
test('opens the create-workspace dialog with DES-246 copy', async ({
|
|
comfyPage
|
|
}) => {
|
|
const page = comfyPage.page
|
|
|
|
await comfyPage.toast.closeToasts()
|
|
await page.getByRole('button', { name: 'Current user' }).click()
|
|
await page.getByTestId('workspace-switcher-trigger').click()
|
|
|
|
await page.getByText('Create a workspace').click()
|
|
|
|
await expect(
|
|
page.getByText(
|
|
'Workspaces keep your projects and files organized. Subscribe to a Team plan to invite members.'
|
|
)
|
|
).toBeVisible()
|
|
await expect(page.getByPlaceholder('Ex: Comfy Org')).toBeVisible()
|
|
})
|
|
})
|