feat(snapshot): implement active workspace tab type tracking (#8487)

* feat(snapshot): implement active workspace tab type tracking and restoration

* feat(snapshot): introduce workspace tab types and update related logic
This commit is contained in:
Sid
2026-07-07 00:09:38 +05:30
committed by GitHub
parent 4ddf65c0c1
commit cbed13a346
6 changed files with 109 additions and 4 deletions

View File

@@ -3,7 +3,8 @@ import {
serializeTab,
serializeActiveTab,
getCollectionEnvironmentPath,
hydrateSnapshotLookups
hydrateSnapshotLookups,
WORKSPACE_TAB_TYPES
} from 'utils/snapshot';
import { normalizePath } from 'utils/common/path';
@@ -133,6 +134,7 @@ export const serializeSnapshot = async (state, options = {}) => {
const existingWorkspace = existingSnapshotLookups.workspacesByPath[normalizePath(workspace.pathname)];
let lastActiveCollectionPathname = null;
let activeWorkspaceTabType = null;
if (isActiveWorkspace) {
const activeTab = tabs.tabs.find((t) => t.uid === tabs.activeTabUid);
@@ -144,8 +146,18 @@ export const serializeSnapshot = async (state, options = {}) => {
lastActiveCollectionPathname = normalizedPathname && normalizedWorkspacePaths.includes(normalizedPathname)
? normalizedPathname
: null;
if (
activeTab
&& workspace.scratchCollectionUid
&& activeTab.collectionUid === workspace.scratchCollectionUid
&& WORKSPACE_TAB_TYPES.has(activeTab.type)
) {
activeWorkspaceTabType = activeTab.type;
}
} else {
lastActiveCollectionPathname = existingWorkspace?.lastActiveCollectionPathname || null;
activeWorkspaceTabType = existingWorkspace?.activeWorkspaceTabType || null;
}
const workspaceSorting = isActiveWorkspace
@@ -157,6 +169,7 @@ export const serializeSnapshot = async (state, options = {}) => {
environment: '',
lastActiveCollectionPathname,
sorting: workspaceSorting,
activeWorkspaceTabType,
collections: [...workspaceCollectionPaths]
});
});

View File

@@ -125,6 +125,40 @@ describe('shouldPreserveCollectionEnvironmentInSnapshot', () => {
});
});
describe('serializeSnapshot workspace tab restoration', () => {
it('records the active workspace tab type when the scratch collection tab is focused', async () => {
const scratchCollectionUid = 'scratch-1';
const state = makeState();
state.workspaces.workspaces[0].scratchCollectionUid = scratchCollectionUid;
state.tabs.tabs = [{ uid: `${scratchCollectionUid}-environments`, collectionUid: scratchCollectionUid, type: 'workspaceEnvironments' }];
state.tabs.activeTabUid = `${scratchCollectionUid}-environments`;
const snapshot = await serializeSnapshot(state, {
getExistingSnapshot: async () => null
});
expect(snapshot.workspaces[0]).toMatchObject({
activeWorkspaceTabType: 'workspaceEnvironments'
});
});
it('does not record an active workspace tab type when a non-workspace tab is focused', async () => {
const scratchCollectionUid = 'scratch-1';
const state = makeState();
state.workspaces.workspaces[0].scratchCollectionUid = scratchCollectionUid;
state.tabs.tabs = [{ uid: 'req-1', collectionUid: COLLECTION_PATH, type: 'http-request' }];
state.tabs.activeTabUid = 'req-1';
const snapshot = await serializeSnapshot(state, {
getExistingSnapshot: async () => null
});
expect(snapshot.workspaces[0]).toMatchObject({
activeWorkspaceTabType: null
});
});
});
describe('serializeSnapshot collection environment preservation', () => {
it('creates a safe first-run snapshot when no existing snapshot is available', async () => {
const snapshot = await serializeSnapshot(makeState(), {

View File

@@ -22,7 +22,7 @@ import {
} from '../app';
import { openConsole, closeConsole, setActiveTab as setActiveDevToolsTab, TAB_IDENFIERS as DEVTOOL_TABS } from '../logs';
import { normalizePath } from 'utils/common/path';
import { hydrateTabs, getActiveTabFromSnapshot, hydrateSnapshotLookups, getCollectionSnapshotFromLookups } from 'utils/snapshot';
import { hydrateTabs, getActiveTabFromSnapshot, hydrateSnapshotLookups, getCollectionSnapshotFromLookups, WORKSPACE_TAB_UID_SUFFIX_BY_TYPE } from 'utils/snapshot';
import toast from 'react-hot-toast';
import { closeAiSidebar } from '../chat';
@@ -640,10 +640,23 @@ export const switchWorkspace = (workspaceUid) => {
));
}));
let requestedWorkspaceTabType = null;
// Add workspace tabs
if (scratchCollection?.uid) {
dispatch(addTab({ uid: `${scratchCollection.uid}-overview`, collectionUid: scratchCollection.uid, type: 'workspaceOverview' }));
dispatch(addTab({ uid: `${scratchCollection.uid}-environments`, collectionUid: scratchCollection.uid, type: 'workspaceEnvironments' }));
requestedWorkspaceTabType = workspaceSnapshot?.activeWorkspaceTabType;
const requestedWorkspaceTabSuffix = WORKSPACE_TAB_UID_SUFFIX_BY_TYPE[requestedWorkspaceTabType];
if (requestedWorkspaceTabSuffix) {
const requestedWorkspaceTabUid = `${scratchCollection.uid}-${requestedWorkspaceTabSuffix}`;
dispatch(addTab({
uid: requestedWorkspaceTabUid,
collectionUid: scratchCollection.uid,
type: requestedWorkspaceTabType
}));
}
}
// Restore active collection from snapshot using lastActiveCollectionPathname
@@ -676,10 +689,10 @@ export const switchWorkspace = (workspaceUid) => {
if (activeTab) {
dispatch(addTab(activeTab));
} else if (scratchCollection?.uid) {
} else if (scratchCollection?.uid && !requestedWorkspaceTabType) {
dispatch(addTab({ uid: `${scratchCollection.uid}-overview`, collectionUid: scratchCollection.uid, type: 'workspaceOverview' }));
}
} else if (scratchCollection?.uid) {
} else if (scratchCollection?.uid && !requestedWorkspaceTabType) {
// No active collection, focus the workspace overview tab
dispatch(addTab({ uid: `${scratchCollection.uid}-overview`, collectionUid: scratchCollection.uid, type: 'workspaceOverview' }));
}

View File

@@ -30,6 +30,13 @@ const IGNORED_TAB_TYPES = new Set([
'v4-migration'
]);
export const WORKSPACE_TAB_UID_SUFFIX_BY_TYPE = {
workspaceOverview: 'overview',
workspaceEnvironments: 'environments'
};
export const WORKSPACE_TAB_TYPES = new Set(Object.keys(WORKSPACE_TAB_UID_SUFFIX_BY_TYPE));
export const SAVE_TRIGGERS = new Map([
['app/setSnapshotReady', null],
['tabs/addTab', null],
@@ -143,6 +150,9 @@ const normalizeWorkspaceSnapshotEntry = (pathname, entry = {}) => {
? entry.lastActiveCollectionPathname
: null,
sorting: typeof entry.sorting === 'string' ? entry.sorting : 'default',
activeWorkspaceTabType: WORKSPACE_TAB_TYPES.has(entry.activeWorkspaceTabType)
? entry.activeWorkspaceTabType
: null,
collections
};
};
@@ -226,6 +236,7 @@ export const hydrateSnapshotLookups = (snapshot = {}) => {
pathname: workspace.pathname,
lastActiveCollectionPathname: workspace.lastActiveCollectionPathname,
sorting: workspace.sorting,
activeWorkspaceTabType: workspace.activeWorkspaceTabType,
collections: workspace.collections
};

View File

@@ -5,6 +5,7 @@ const yup = require('yup');
const SNAPSHOT_VERSION = '0.0.1';
const ENV_FILE_EXTENSIONS = ['bru', 'yml', 'yaml'];
const WORKSPACE_TAB_TYPES = ['workspaceOverview', 'workspaceEnvironments'];
const isObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
@@ -71,6 +72,7 @@ const workspaceSchema = yup.object({
pathname: yup.string().required(),
environment: yup.string().defined(),
lastActiveCollectionPathname: yup.string().nullable(),
activeWorkspaceTabType: yup.string().oneOf([...WORKSPACE_TAB_TYPES, null]).nullable(),
sorting: yup.mixed().oneOf(['alphabetical', 'reverseAlphabetical', 'default']),
collections: yup.array().of(yup.string()).optional()
});
@@ -193,6 +195,7 @@ class SnapshotManager {
this.store.delete('activeWorkspacePath');
this.store.set('workspaces', (this.store.store?.workspaces ?? []).map((d) => {
d.lastActiveCollectionPathname = undefined;
d.activeWorkspaceTabType = undefined;
return d;
}));
this.store.set('collections', (this.store.store?.collections ?? []).map((d) => {
@@ -461,6 +464,9 @@ class SnapshotManager {
lastActiveCollectionPathname: typeof workspace.lastActiveCollectionPathname === 'string'
? workspace.lastActiveCollectionPathname
: null,
activeWorkspaceTabType: WORKSPACE_TAB_TYPES.includes(workspace.activeWorkspaceTabType)
? workspace.activeWorkspaceTabType
: null,
sorting: typeof workspace.sorting === 'string' ? workspace.sorting : 'default',
collections
};

View File

@@ -55,4 +55,32 @@ test.describe('Snapshot: Global Tab Restoration', () => {
await closeElectronApp(app2);
});
});
test('workspace-level active tab is restored after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-workspace-active-tab');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Switch to workspace environments tab', async () => {
const locators = buildCommonLocators(page);
await locators.tabs.requestTab('Environments').click();
await expect(locators.tabs.activeRequestTab()).toContainText('Environments');
});
await test.step('Close and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify workspace environments is restored as active tab', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators2 = buildCommonLocators(page2);
await expect(locators2.tabs.activeRequestTab()).toContainText('Environments', { timeout: 15000 });
await closeElectronApp(app2);
});
});
});