From 9be9c666ecaccce4609835c40c88ce2638876f84 Mon Sep 17 00:00:00 2001 From: KCM Date: Fri, 1 May 2026 21:22:57 -0500 Subject: [PATCH 1/2] refactor: better workspace drawer ux. --- .../github-pr-drawer/open-pr-create.spec.ts | 85 +++++++++++++++++++ .../workspace/workspaces-drawer/drawer.js | 16 +++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/playwright/github-pr-drawer/open-pr-create.spec.ts b/playwright/github-pr-drawer/open-pr-create.spec.ts index d044033..64c59ad 100644 --- a/playwright/github-pr-drawer/open-pr-create.spec.ts +++ b/playwright/github-pr-drawer/open-pr-create.spec.ts @@ -1108,6 +1108,10 @@ test('Local New workspace always creates a new stored workspace snapshot', async const initialLocalRecordCount = await countLocalRecords() await page.getByRole('button', { name: 'New workspace', exact: true }).click() + await expect(page.getByRole('complementary', { name: 'Workspaces' })).toBeHidden() + + await page.getByRole('button', { name: 'Workspaces' }).click() + await expect(page.getByRole('button', { name: 'Remove', exact: true })).toBeDisabled() await expect.poll(async () => countLocalRecords()).toBe(initialLocalRecordCount + 1) }) @@ -1151,6 +1155,7 @@ test('Non-Local New workspace forks a new repository-scoped workspace when entri page.getByRole('button', { name: 'New workspace', exact: true }), ).toBeVisible() await page.getByRole('button', { name: 'New workspace', exact: true }).click() + await expect(page.getByRole('complementary', { name: 'Workspaces' })).toBeHidden() await expect.poll(async () => countRepositoryRecords()).toBe(initialRepositoryCount + 1) @@ -1174,6 +1179,86 @@ test('Non-Local New workspace forks a new repository-scoped workspace when entri expect(String(forkedRepositoryRecord?.head ?? '')).not.toBe(seededHead) }) +test('Removing a non-active workspace reselects the active workspace in Workspaces select', async ({ + page, +}) => { + const activeWorkspaceId = 'active_workspace_select_fallback_target' + + await waitForAppReady(page, `${appEntryPath}`) + + await seedLocalWorkspaceContexts(page, [ + { + id: activeWorkspaceId, + repo: '', + base: 'main', + head: 'feat/active-workspace', + prTitle: 'Active workspace', + prNumber: null, + prContextState: 'inactive', + }, + { + id: 'workspace_to_remove_from_drawer', + repo: '', + base: 'main', + head: 'feat/removable-workspace', + prTitle: 'Removable workspace', + prNumber: null, + prContextState: 'inactive', + }, + ]) + + await page.reload() + await waitForAppReady(page, `${appEntryPath}`) + await connectByotWithSingleRepo(page) + + await openStoredWorkspaceContextById(page, activeWorkspaceId, { + repositoryFilter: '__local__', + }) + + await page.getByRole('button', { name: 'Workspaces' }).click() + await selectWorkspacesRepositoryFilter(page, '__local__') + + const storedWorkspaceSelect = page.locator('#workspaces-select') + const removeWorkspaceButton = page.getByRole('button', { + name: 'Remove', + exact: true, + }) + + const resolveRemovableWorkspaceId = () => + storedWorkspaceSelect.evaluate((element, activeId) => { + if (!(element instanceof HTMLSelectElement)) { + return '' + } + + const candidates = Array.from(element.options) + .map(option => option.value) + .filter(value => value && value !== activeId) + + return candidates[0] ?? '' + }, activeWorkspaceId) + + await expect.poll(resolveRemovableWorkspaceId).not.toBe('') + const removableWorkspaceId = await resolveRemovableWorkspaceId() + + await storedWorkspaceSelect.selectOption(removableWorkspaceId) + await expect(storedWorkspaceSelect).toHaveValue(removableWorkspaceId) + await expect(removeWorkspaceButton).toBeEnabled() + await removeWorkspaceButton.click() + + const dialog = page.locator('#clear-confirm-dialog') + await expect(dialog).toBeVisible() + await dialog.locator('button[value="confirm"]').evaluate(element => { + if (element instanceof HTMLButtonElement) { + element.click() + } + }) + + await expect + .poll(async () => storedWorkspaceSelect.inputValue()) + .toBe(activeWorkspaceId) + await expect(removeWorkspaceButton).toBeDisabled() +}) + test('Switching Workspaces repository scope to Local keeps inactive record repo and shows it as local in drawer', async ({ page, }) => { diff --git a/src/modules/workspace/workspaces-drawer/drawer.js b/src/modules/workspace/workspaces-drawer/drawer.js index 6f98951..c5dcae5 100644 --- a/src/modules/workspace/workspaces-drawer/drawer.js +++ b/src/modules/workspace/workspaces-drawer/drawer.js @@ -149,7 +149,11 @@ export const createWorkspacesDrawer = ({ selectInput instanceof HTMLSelectElement ? toSafeText(selectInput.value) : toSafeText(selectedId) + const activeWorkspaceId = + typeof getActiveWorkspaceId === 'function' ? toSafeText(getActiveWorkspaceId()) : '' const hasSelection = normalizedSelectedId.length > 0 + const isSelectedWorkspaceActive = + hasSelection && activeWorkspaceId && normalizedSelectedId === activeWorkspaceId const canCreateWorkspace = typeof onCreateWorkspace === 'function' const canInitializeWorkspace = typeof onInitializeWorkspace === 'function' const hasStoredWorkspaces = @@ -185,7 +189,7 @@ export const createWorkspacesDrawer = ({ } if (removeButton instanceof HTMLButtonElement) { - removeButton.disabled = !hasSelection + removeButton.disabled = !hasSelection || isSelectedWorkspaceActive } } @@ -325,7 +329,14 @@ export const createWorkspacesDrawer = ({ } if (!entries.some(entry => toSafeText(entry?.id) === selectedId)) { - selectedId = '' + const activeWorkspaceId = + typeof getActiveWorkspaceId === 'function' + ? toSafeText(getActiveWorkspaceId()) + : '' + const hasActiveWorkspaceEntry = entries.some( + entry => toSafeText(entry?.id) === activeWorkspaceId, + ) + selectedId = hasActiveWorkspaceEntry ? activeWorkspaceId : '' } renderOptions() @@ -466,6 +477,7 @@ export const createWorkspacesDrawer = ({ typeof getActiveWorkspaceId === 'function' ? toSafeText(getActiveWorkspaceId()) : '' setStatus('Created workspace.', 'neutral') await refresh({ preserveSelection: Boolean(selectedId) }) + closeDrawer() }) openButton?.addEventListener('click', async () => { From bf4025791426393797d9cfc16eee22ee0e7aaaa8 Mon Sep 17 00:00:00 2001 From: KCM Date: Fri, 1 May 2026 21:36:53 -0500 Subject: [PATCH 2/2] refactor: coerce to boolean. --- src/modules/workspace/workspaces-drawer/drawer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/workspace/workspaces-drawer/drawer.js b/src/modules/workspace/workspaces-drawer/drawer.js index c5dcae5..76695d8 100644 --- a/src/modules/workspace/workspaces-drawer/drawer.js +++ b/src/modules/workspace/workspaces-drawer/drawer.js @@ -153,7 +153,9 @@ export const createWorkspacesDrawer = ({ typeof getActiveWorkspaceId === 'function' ? toSafeText(getActiveWorkspaceId()) : '' const hasSelection = normalizedSelectedId.length > 0 const isSelectedWorkspaceActive = - hasSelection && activeWorkspaceId && normalizedSelectedId === activeWorkspaceId + hasSelection && + Boolean(activeWorkspaceId) && + normalizedSelectedId === activeWorkspaceId const canCreateWorkspace = typeof onCreateWorkspace === 'function' const canInitializeWorkspace = typeof onInitializeWorkspace === 'function' const hasStoredWorkspaces =