Skip to content

Commit 9854485

Browse files
feat: rename active local workspace.
1 parent b247622 commit 9854485

5 files changed

Lines changed: 154 additions & 4 deletions

File tree

playwright/github-byot-ai.spec.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,129 @@ test('Local workspace can be renamed from Workspaces drawer', async ({ page }) =
418418
.toBe(renamedTitle)
419419
})
420420

421+
test('Active Local non-PR workspace can be renamed from Workspaces drawer', async ({
422+
page,
423+
}) => {
424+
const activeWorkspaceId = 'active_local_workspace_rename_allowed'
425+
const originalTitle = 'Active local rename original title'
426+
const renamedTitle = 'Active local rename updated title'
427+
428+
await waitForAppReady(page)
429+
430+
await seedLocalWorkspaceContexts(page, [
431+
{
432+
id: activeWorkspaceId,
433+
repo: '',
434+
workspaceScope: 'local',
435+
head: 'feat/active-local-rename-allowed',
436+
prTitle: originalTitle,
437+
prContextState: 'inactive',
438+
prNumber: null,
439+
tabs: [
440+
{
441+
id: 'component',
442+
path: 'src/component.tsx',
443+
language: 'tsx',
444+
role: 'component',
445+
content: 'export const App = () => <main>active local rename allowed</main>',
446+
order: 0,
447+
source: 'workspace',
448+
dirty: false,
449+
},
450+
],
451+
activeTabId: 'component',
452+
},
453+
])
454+
455+
await page.reload()
456+
await waitForAppReady(page)
457+
458+
const workspacesToggle = page.getByRole('button', {
459+
name: 'Workspaces',
460+
exact: true,
461+
})
462+
await workspacesToggle.click()
463+
464+
const workspaceSelect = page.getByLabel('Stored workspace')
465+
const renameButton = page.getByRole('button', { name: 'Rename', exact: true })
466+
await expect(renameButton).toBeVisible()
467+
468+
await expect(workspaceSelect).toHaveValue(activeWorkspaceId)
469+
await expect(renameButton).toBeEnabled()
470+
471+
page.once('dialog', async dialog => {
472+
expect(dialog.type()).toBe('prompt')
473+
expect(dialog.defaultValue()).toBe(originalTitle)
474+
await dialog.accept(renamedTitle)
475+
})
476+
477+
await renameButton.click()
478+
await expect(page.locator('#workspaces-status')).toContainText('Renamed workspace.')
479+
480+
const records = await getAllWorkspaceRecords(page)
481+
const renamedRecord = records.find(record => record?.id === activeWorkspaceId)
482+
483+
expect(renamedRecord).toBeTruthy()
484+
expect(typeof renamedRecord?.prTitle === 'string' ? renamedRecord.prTitle : '').toBe(
485+
renamedTitle,
486+
)
487+
488+
const selectedLabelText = await page
489+
.locator('#workspaces-select option:checked')
490+
.textContent()
491+
expect(String(selectedLabelText ?? '').trim()).toBe(renamedTitle)
492+
await expect(page.locator('#workspace-context-status')).toContainText(renamedTitle)
493+
})
494+
495+
test('Active Local PR-associated workspace cannot be renamed from Workspaces drawer', async ({
496+
page,
497+
}) => {
498+
const activeWorkspaceId = 'active_local_workspace_rename_blocked_pr_associated'
499+
500+
await waitForAppReady(page)
501+
502+
await seedLocalWorkspaceContexts(page, [
503+
{
504+
id: activeWorkspaceId,
505+
repo: '',
506+
workspaceScope: 'local',
507+
head: 'feat/active-local-rename-blocked',
508+
prTitle: 'PR-associated local workspace',
509+
prContextState: 'active',
510+
prNumber: 97,
511+
tabs: [
512+
{
513+
id: 'component',
514+
path: 'src/component.tsx',
515+
language: 'tsx',
516+
role: 'component',
517+
content: 'export const App = () => <main>active local rename blocked</main>',
518+
order: 0,
519+
source: 'workspace',
520+
dirty: false,
521+
},
522+
],
523+
activeTabId: 'component',
524+
},
525+
])
526+
527+
await page.reload()
528+
await waitForAppReady(page)
529+
530+
const workspacesToggle = page.getByRole('button', {
531+
name: 'Workspaces',
532+
exact: true,
533+
})
534+
await workspacesToggle.click()
535+
536+
const workspaceSelect = page.getByLabel('Stored workspace')
537+
const renameButton = page.getByRole('button', { name: 'Rename', exact: true })
538+
await expect(renameButton).toBeVisible()
539+
540+
await expect(workspaceSelect).toHaveValue(activeWorkspaceId)
541+
await expect(renameButton).toBeDisabled()
542+
})
543+
421544
test('chat stays usable after opening a Local workspace with PAT connected', async ({
422545
page,
423546
}) => {

src/app.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ const { syncActiveWorkspaceRepositoryScope, forkWorkspaceFromCurrentState } =
953953
setActiveWorkspacePersistedMetadata: ({ prTitle, head } = {}) => {
954954
activeWorkspacePersistedPrTitle = toNonEmptyWorkspaceText(prTitle)
955955
activeWorkspacePersistedHeadBranch = toNonEmptyWorkspaceText(head)
956+
workspaceContextStatusController.render()
956957
},
957958
})
958959

@@ -1168,6 +1169,11 @@ const githubWorkflows = createGitHubWorkflowsSetup({
11681169
workspaceStorage,
11691170
getActiveWorkspaceRecordId: () => activeWorkspaceRecordId,
11701171
getActiveWorkspaceDisplayLabel,
1172+
setActiveWorkspacePersistedMetadata: ({ prTitle, head } = {}) => {
1173+
activeWorkspacePersistedPrTitle = toNonEmptyWorkspaceText(prTitle)
1174+
activeWorkspacePersistedHeadBranch = toNonEmptyWorkspaceText(head)
1175+
workspaceContextStatusController.render()
1176+
},
11711177
setActiveWorkspaceRecordId,
11721178
setActiveWorkspaceCreatedAt: value => (activeWorkspaceCreatedAt = value),
11731179
buildWorkspaceRecordSnapshot,

src/modules/app-core/github-workflows-setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const createGitHubWorkflowsSetup = ({
2424
setActiveWorkspaceCreatedAt: workspace.setActiveWorkspaceCreatedAt,
2525
buildWorkspaceRecordSnapshot: workspace.buildWorkspaceRecordSnapshot,
2626
getActiveWorkspaceDisplayLabel: workspace.getActiveWorkspaceDisplayLabel,
27+
setActiveWorkspacePersistedMetadata: workspace.setActiveWorkspacePersistedMetadata,
2728
listLocalContextRecords: workspace.listLocalContextRecords,
2829
refreshLocalContextOptions: workspace.refreshLocalContextOptions,
2930
applyWorkspaceRecord: workspace.applyWorkspaceRecord,

src/modules/app-core/github-workflows.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const initializeGitHubWorkflows = ({
5151
workspaceStorage,
5252
getActiveWorkspaceRecordId,
5353
getActiveWorkspaceDisplayLabel,
54+
setActiveWorkspacePersistedMetadata,
5455
setActiveWorkspaceRecordId,
5556
setActiveWorkspaceCreatedAt,
5657
buildWorkspaceRecordSnapshot,
@@ -560,14 +561,19 @@ const initializeGitHubWorkflows = ({
560561
return false
561562
}
562563

563-
if (getActiveWorkspaceRecordId() === workspaceId) {
564+
const hasWorkspacePrNumber =
565+
typeof record.prNumber === 'number' && Number.isFinite(record.prNumber)
566+
const isPrAssociatedWorkspace = hasWorkspacePrNumber
567+
if (isPrAssociatedWorkspace) {
564568
workspacesDrawerController?.setStatus(
565-
'Open a different workspace before renaming this one.',
569+
'Use Pull Request controls to rename PR-associated workspaces.',
566570
'error',
567571
)
568572
return false
569573
}
570574

575+
const isActiveWorkspace = getActiveWorkspaceRecordId() === workspaceId
576+
571577
const currentWorkspaceName =
572578
toSafeWorkspaceText(record.prTitle) ||
573579
toSafeWorkspaceText(record.head) ||
@@ -601,6 +607,16 @@ const initializeGitHubWorkflows = ({
601607
lastModified: Date.now(),
602608
})
603609

610+
if (
611+
isActiveWorkspace &&
612+
typeof setActiveWorkspacePersistedMetadata === 'function'
613+
) {
614+
setActiveWorkspacePersistedMetadata({
615+
prTitle: nextWorkspaceName,
616+
head: toSafeWorkspaceText(record.head),
617+
})
618+
}
619+
604620
await refreshLocalContextOptions()
605621
return true
606622
} catch {

src/modules/workspace/workspaces-drawer/drawer.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,15 @@ export const createWorkspacesDrawer = ({
7878
}
7979

8080
const isInactiveWithoutPrNumber = workspace => {
81-
const state = toSafeText(workspace?.prContextState).toLowerCase()
81+
const state = toSafeText(workspace?.prContextState).toLowerCase() || 'inactive'
8282
const hasPrNumber =
8383
typeof workspace?.prNumber === 'number' && Number.isFinite(workspace.prNumber)
8484
return state === 'inactive' && !hasPrNumber
8585
}
8686

87+
const hasFinitePrNumber = workspace =>
88+
typeof workspace?.prNumber === 'number' && Number.isFinite(workspace.prNumber)
89+
8790
const shouldRenderAsLocalEntry = workspace => {
8891
if (toSafeWorkspaceScope(workspace) === localWorkspaceScopeValue) {
8992
return true
@@ -157,14 +160,15 @@ export const createWorkspacesDrawer = ({
157160
const selectedWorkspaceScope = toSafeWorkspaceScope(selectedEntry)
158161
const isSelectedLocalWorkspace =
159162
hasSelection && selectedWorkspaceScope === localWorkspaceScopeValue
163+
const isSelectedWorkspaceNonPr = hasSelection && !hasFinitePrNumber(selectedEntry)
160164
const isSelectedWorkspaceActive =
161165
hasSelection &&
162166
Boolean(activeWorkspaceId) &&
163167
normalizedSelectedId === activeWorkspaceId
164168
const canRenameWorkspace =
165169
typeof onRenameSelected === 'function' &&
166170
isSelectedLocalWorkspace &&
167-
!isSelectedWorkspaceActive
171+
isSelectedWorkspaceNonPr
168172
const canCreateWorkspace = typeof onCreateWorkspace === 'function'
169173
const canInitializeWorkspace = typeof onInitializeWorkspace === 'function'
170174
const hasStoredWorkspaces =

0 commit comments

Comments
 (0)