From db408896c86b6cd75974ac3981a05480679440b0 Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Thu, 30 Apr 2026 13:54:53 -0700 Subject: [PATCH 1/8] Ensure blank sequence opened on new page shows correct right side tabs --- e2e-tests/tests/workspace.test.ts | 8 ++++++++ src/routes/workspaces/[workspaceId]/+page.svelte | 10 +++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/e2e-tests/tests/workspace.test.ts b/e2e-tests/tests/workspace.test.ts index b1cfb9ebbf..de6039621c 100644 --- a/e2e-tests/tests/workspace.test.ts +++ b/e2e-tests/tests/workspace.test.ts @@ -83,6 +83,14 @@ test.describe.serial('Workspace', () => { await workspace.pageLoadingLocatorWithData.waitFor({ state: 'detached' }); }); + test('Right icon rail shows sequence tabs when workspace loads with no file selected', async () => { + // With no file in the URL the page defaults to a SequenceEditor, so the right + // icon rail should expose Selected Command and Command Dictionary tabs alongside Metadata. + await expect(setup.page.getByRole('button', { exact: true, name: 'Metadata' })).toBeVisible(); + await expect(setup.page.getByRole('button', { exact: true, name: 'Selected Command' })).toBeVisible(); + await expect(setup.page.getByRole('button', { exact: true, name: 'Command Dictionary' })).toBeVisible(); + }); + test('Workspace header menu should be accessible', async () => { await expect(workspace.workspaceContextMenuButton).toBeVisible(); await workspace.openWorkspaceContextMenu(); diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index 7d78bbe878..e8d67a4f92 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -277,9 +277,8 @@ $: activeFileMetadata = ($activeDocumentPath && workspaceTreeMap[$activeDocumentPath]?.metadata) || null; $: activeFileIsSequence = - $activeDocumentPath !== null && - $activeDocument.type !== null && - $activeDocument.type === WorkspaceContentType.Sequence; + $activeDocumentPath === null || + ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); $: commandInfoMapper = $sequenceAdaptation.input.commandInfoMapper; $: isFileReadOnly = activeFileMetadata?.readOnly ?? false; @@ -1410,9 +1409,6 @@ {:else} {@const isTextOrEmpty = $activeDocumentPath === null || isTextFile(workspaceTreeMap[$activeDocumentPath]?.type)} - {@const isSequenceFile = - $activeDocumentPath === null || - ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence)}
{#if showLoadingSpinner && isTextOrEmpty}
{/if} - {#if isTextOrEmpty && isSequenceFile} + {#if isTextOrEmpty && activeFileIsSequence}
Date: Thu, 30 Apr 2026 16:32:43 -0700 Subject: [PATCH 2/8] use exact selector for Metadata button to avoid name conflict --- e2e-tests/fixtures/Workspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/fixtures/Workspace.ts b/e2e-tests/fixtures/Workspace.ts index 400bb11332..2882e2cc80 100644 --- a/e2e-tests/fixtures/Workspace.ts +++ b/e2e-tests/fixtures/Workspace.ts @@ -279,7 +279,7 @@ export class Workspace { this.metadataCancelButton = page.locator('.user-metadata-editor + div').getByRole('button', { name: 'Cancel' }); this.metadataPanel = page.locator('.user-metadata-editor').first(); this.metadataSaveButton = page.locator('.user-metadata-editor + div').getByRole('button', { name: 'Save' }); - this.metadataTabButton = page.getByRole('button', { name: 'Metadata' }); + this.metadataTabButton = page.getByRole('button', { exact: true, name: 'Metadata' }); this.navButtonSequences = page.locator('.nav-button:has-text("Sequences")'); this.navButtonSequencesMenu = this.navButtonSequences.getByRole('menu'); this.page = page; From 9c88c74ad77ed0f4c7ee95942fb9220c3b9ada8c Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Thu, 7 May 2026 13:11:26 -0700 Subject: [PATCH 3/8] Behavior fix and deflake --- e2e-tests/fixtures/Workspace.ts | 7 +++++-- e2e-tests/tests/workspace.test.ts | 2 +- .../workspaces/[workspaceId]/+page.svelte | 17 ++++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/e2e-tests/fixtures/Workspace.ts b/e2e-tests/fixtures/Workspace.ts index 2882e2cc80..8d0bc09ad6 100644 --- a/e2e-tests/fixtures/Workspace.ts +++ b/e2e-tests/fixtures/Workspace.ts @@ -49,8 +49,11 @@ export class Workspace { await this.searchInput.clear(); } - async clickFile(name: string): Promise { - await this.workspaceFileGrid.getByRole('row', { name }).click(); + async clickFile(name: string, options?: { force?: boolean }): Promise { + // `force: true` is for callers that expect the click to immediately summon a modal + // (e.g. unsaved-changes confirmation) — Playwright's hit-test would otherwise see the + // modal backdrop and retry until timeout, even though the original click registered. + await this.workspaceFileGrid.getByRole('row', { name }).click(options); } async createFolder(folderPath?: string): Promise { diff --git a/e2e-tests/tests/workspace.test.ts b/e2e-tests/tests/workspace.test.ts index de6039621c..bd3dc33039 100644 --- a/e2e-tests/tests/workspace.test.ts +++ b/e2e-tests/tests/workspace.test.ts @@ -287,7 +287,7 @@ test.describe.serial('Workspace', () => { // Try to navigate to second file await workspace.searchForFileAndWait(file2); - await workspace.clickFile(file2); + await workspace.clickFile(file2, { force: true }); // Should show confirmation modal const modal = setup.page.locator('#modal-container'); diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index e8d67a4f92..dbb12a119d 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -279,14 +279,21 @@ $: activeFileIsSequence = $activeDocumentPath === null || ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); + $: selectedFileIsSequence = + $activeDocumentPath !== null && + $activeDocument.type !== null && + $activeDocument.type === WorkspaceContentType.Sequence; $: commandInfoMapper = $sequenceAdaptation.input.commandInfoMapper; $: isFileReadOnly = activeFileMetadata?.readOnly ?? false; - // Switch right panel tab when file type changes - let previousActiveFileIsSequence: boolean = activeFileIsSequence; - $: if (activeFileIsSequence !== previousActiveFileIsSequence) { - previousActiveFileIsSequence = activeFileIsSequence; - if (!activeFileIsSequence) { + // Switch right panel tab when the selected file's sequence-ness changes. + // Driven by selectedFileIsSequence (narrow) rather than activeFileIsSequence (wide), + // so the empty/no-file state — which renders SequenceEditor as a default canvas — does + // not suppress the tab switch when the user actually picks a sequence file. + let previousSelectedFileIsSequence: boolean = selectedFileIsSequence; + $: if (selectedFileIsSequence !== previousSelectedFileIsSequence) { + previousSelectedFileIsSequence = selectedFileIsSequence; + if (!selectedFileIsSequence) { rightPanelActiveTab = 'metadata'; } else { rightPanelActiveTab = 'command'; From 9a688270f015849fe2f30560efbe3a426d2d4255 Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Mon, 11 May 2026 09:28:04 -0700 Subject: [PATCH 4/8] Refactor --- .../workspaces/[workspaceId]/+page.svelte | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index dbb12a119d..f6fc1a5b08 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -142,6 +142,7 @@ const resizableHandleClass = 'w-[3px] hover:after:bg-neutral-300 hover:after:transition-all hover:after:delay-[400ms] data-[active]:after:bg-neutral-300 data-[active]:after:transition-all'; + let actionDetailIsDirty: boolean = false; let availableActionsForActiveFile: ActionParameterPair[] = []; let panelsReady: boolean = false; let allActionsForWorkspace: ActionDefinition[] = []; @@ -180,7 +181,6 @@ let workspaceTree: WorkspaceTreeNode | null = null; let workspaceTreeMap: WorkspaceTreeMap = {}; let workspaceFileList: WorkspaceTreeNodeWithFullPath[] = []; - let actionDetailIsDirty: boolean = false; if (initialActionRunIdParam) { const runId = parseInt(initialActionRunIdParam, 10); @@ -279,21 +279,14 @@ $: activeFileIsSequence = $activeDocumentPath === null || ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); - $: selectedFileIsSequence = - $activeDocumentPath !== null && - $activeDocument.type !== null && - $activeDocument.type === WorkspaceContentType.Sequence; $: commandInfoMapper = $sequenceAdaptation.input.commandInfoMapper; $: isFileReadOnly = activeFileMetadata?.readOnly ?? false; - // Switch right panel tab when the selected file's sequence-ness changes. - // Driven by selectedFileIsSequence (narrow) rather than activeFileIsSequence (wide), - // so the empty/no-file state — which renders SequenceEditor as a default canvas — does - // not suppress the tab switch when the user actually picks a sequence file. - let previousSelectedFileIsSequence: boolean = selectedFileIsSequence; - $: if (selectedFileIsSequence !== previousSelectedFileIsSequence) { - previousSelectedFileIsSequence = selectedFileIsSequence; - if (!selectedFileIsSequence) { + // Drafts count as sequences, so the tab flips to 'command' on initial draft load too. + let previousActiveFileIsSequence: boolean = activeFileIsSequence; + $: if (activeFileIsSequence !== previousActiveFileIsSequence) { + previousActiveFileIsSequence = activeFileIsSequence; + if (!activeFileIsSequence) { rightPanelActiveTab = 'metadata'; } else { rightPanelActiveTab = 'command'; From d9c62ecd8d04be7137ed22d3a48175f1da7122f4 Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Mon, 11 May 2026 10:22:21 -0700 Subject: [PATCH 5/8] Fix --- src/routes/workspaces/[workspaceId]/+page.svelte | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index f6fc1a5b08..7ab7d297ca 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -277,12 +277,16 @@ $: activeFileMetadata = ($activeDocumentPath && workspaceTreeMap[$activeDocumentPath]?.metadata) || null; $: activeFileIsSequence = - $activeDocumentPath === null || - ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); + $activeDocumentPath !== null && + $activeDocument.type !== null && + $activeDocument.type === WorkspaceContentType.Sequence; + // True whenever the editor pane is rendering a SequenceEditor — including the draft file + // default case. Drives whether the right rail exposes the Selected Command / Command + // Dictionary tabs, independent of the strict file-type check above which drives tab transitions. + $: editorShowsSequence = $activeDocumentPath === null || activeFileIsSequence; $: commandInfoMapper = $sequenceAdaptation.input.commandInfoMapper; $: isFileReadOnly = activeFileMetadata?.readOnly ?? false; - // Drafts count as sequences, so the tab flips to 'command' on initial draft load too. let previousActiveFileIsSequence: boolean = activeFileIsSequence; $: if (activeFileIsSequence !== previousActiveFileIsSequence) { previousActiveFileIsSequence = activeFileIsSequence; @@ -1417,7 +1421,7 @@
{/if} - {#if isTextOrEmpty && activeFileIsSequence} + {#if isTextOrEmpty && editorShowsSequence}
{/if}
From 005f6387b3c568b2dc8ecba1c28f14300438990c Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Mon, 11 May 2026 13:10:01 -0700 Subject: [PATCH 6/8] Fix --- .../workspaces/[workspaceId]/+page.svelte | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index 7ab7d297ca..2bf3ca6b42 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -276,17 +276,17 @@ } $: activeFileMetadata = ($activeDocumentPath && workspaceTreeMap[$activeDocumentPath]?.metadata) || null; + // "Sequence mode" — true when the editor pane is rendering a SequenceEditor. A blank-load + // (no file open) counts as sequence mode because we default to an empty SequenceEditor. $: activeFileIsSequence = - $activeDocumentPath !== null && - $activeDocument.type !== null && - $activeDocument.type === WorkspaceContentType.Sequence; - // True whenever the editor pane is rendering a SequenceEditor — including the draft file - // default case. Drives whether the right rail exposes the Selected Command / Command - // Dictionary tabs, independent of the strict file-type check above which drives tab transitions. - $: editorShowsSequence = $activeDocumentPath === null || activeFileIsSequence; + $activeDocumentPath === null || + ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); $: commandInfoMapper = $sequenceAdaptation.input.commandInfoMapper; $: isFileReadOnly = activeFileMetadata?.readOnly ?? false; + // Auto-switch the right-panel tab only when the editor crosses between sequence mode and + // non-sequence mode. Switching between two sequence files (or between blank and a sequence + // file) preserves whatever tab the user last chose. let previousActiveFileIsSequence: boolean = activeFileIsSequence; $: if (activeFileIsSequence !== previousActiveFileIsSequence) { previousActiveFileIsSequence = activeFileIsSequence; @@ -1421,7 +1421,7 @@
{/if} - {#if isTextOrEmpty && editorShowsSequence} + {#if isTextOrEmpty && activeFileIsSequence}
{/if}
From 09d8b9b6dabb1e0cbe324c8554baf589ef8ef4f4 Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Mon, 11 May 2026 13:11:10 -0700 Subject: [PATCH 7/8] Fix --- src/routes/workspaces/[workspaceId]/+page.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/workspaces/[workspaceId]/+page.svelte b/src/routes/workspaces/[workspaceId]/+page.svelte index 2bf3ca6b42..ba3fabaf53 100644 --- a/src/routes/workspaces/[workspaceId]/+page.svelte +++ b/src/routes/workspaces/[workspaceId]/+page.svelte @@ -142,6 +142,7 @@ const resizableHandleClass = 'w-[3px] hover:after:bg-neutral-300 hover:after:transition-all hover:after:delay-[400ms] data-[active]:after:bg-neutral-300 data-[active]:after:transition-all'; + let activeFileIsSequence: boolean = false; let actionDetailIsDirty: boolean = false; let availableActionsForActiveFile: ActionParameterPair[] = []; let panelsReady: boolean = false; @@ -276,8 +277,6 @@ } $: activeFileMetadata = ($activeDocumentPath && workspaceTreeMap[$activeDocumentPath]?.metadata) || null; - // "Sequence mode" — true when the editor pane is rendering a SequenceEditor. A blank-load - // (no file open) counts as sequence mode because we default to an empty SequenceEditor. $: activeFileIsSequence = $activeDocumentPath === null || ($activeDocument.type !== null && $activeDocument.type === WorkspaceContentType.Sequence); From 381b3655488bf47ae5f61dbf6eb46ace45f8fca6 Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Mon, 11 May 2026 13:44:43 -0700 Subject: [PATCH 8/8] Deflake --- e2e-tests/fixtures/Workspace.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/e2e-tests/fixtures/Workspace.ts b/e2e-tests/fixtures/Workspace.ts index 8d0bc09ad6..f9e7f9644e 100644 --- a/e2e-tests/fixtures/Workspace.ts +++ b/e2e-tests/fixtures/Workspace.ts @@ -225,12 +225,15 @@ export class Workspace { /** * Open the right-side metadata panel by clicking the metadata tab icon. - * If the panel is already open on the metadata tab, this is a no-op. + * If the panel is already open on the metadata tab, this is a no-op — clicking again + * would toggle the panel closed. */ async openMetadataPanel(): Promise { - // Click the Metadata tab button in the right icon rail - await this.metadataTabButton.click(); - // Wait for the metadata panel content to appear + // The icon rail button sets data-active="true" iff metadata tab is active AND panel is open. + const isAlreadyOpen = (await this.metadataTabButton.getAttribute('data-active')) === 'true'; + if (!isAlreadyOpen) { + await this.metadataTabButton.click(); + } await this.page.getByText('User metadata', { exact: true }).waitFor({ state: 'visible', timeout: 5000 }); }