feat: merge titlebar and tab bar into single header with dynamic workspace alignment

- Merge titlebar and tab bar into a single unified top bar (.titlebar)
- Remove redundant floating workspace button that caused overlap on collapsed sidebar
- Ensure tab bar left aligns with terminal window when workspace sidebar is expanded
- Allow tab bar to sit naturally at leftmost position when workspace sidebar is collapsed
- Update test runner suite to cover merged titlebar and alignment behavior
This commit is contained in:
2026-09-24 22:57:47 +08:00
parent 5a032b13ec
commit 2db388c2f2
8 changed files with 143 additions and 174 deletions
+42 -24
View File
@@ -150,9 +150,9 @@ app.whenReady().then(async () => {
`);
// ==========================================
// Suite 2: Workspace Hierarchy & Stage Alignment
// Suite 2: Merged Titlebar & Workspace Alignment
// ==========================================
runner.suite('2. 工作区层级结构与视口对齐验证 (UI Architecture)');
runner.suite('2. 顶栏与标签页栏合二为一与工作区对齐验证 (UI Architecture)');
await runner.test('标题栏与工作区标识层级正确 (Titlebar Header)', async () => {
const res = await win.webContents.executeJavaScript(`
@@ -172,50 +172,68 @@ app.whenReady().then(async () => {
if (!res.wsName) throw new Error('Active workspace tag name is empty');
});
await runner.test('工作区终端舞台包含独立头部标签条与视口 (.workspace-stage)', async () => {
await runner.test('顶栏和标签页栏合二为一 (Merged Titlebar and Tab Bar)', async () => {
const res = await win.webContents.executeJavaScript(`
(() => {
const stage = document.querySelector('.workspace-stage');
const header = document.querySelector('.workspace-header');
const viewport = document.getElementById('main-viewport');
const titlebar = document.querySelector('.titlebar');
const tabBarContainer = document.getElementById('tab-bar-container');
const tabBar = document.getElementById('tab-bar');
const newTabBtn = document.getElementById('btn-new-tab');
const viewport = document.getElementById('main-viewport');
const hasOldStage = !!document.querySelector('.workspace-header');
return {
hasStage: !!stage,
hasHeader: !!header,
titlebarHasContainer: titlebar?.contains(tabBarContainer),
containerHasTabs: tabBarContainer?.contains(tabBar),
containerHasBtn: tabBarContainer?.contains(newTabBtn),
hasViewport: !!viewport,
tabsInHeader: header?.contains(tabBar),
btnInHeader: header?.contains(newTabBtn),
hasOldHeader: hasOldStage
};
})()
`);
if (!res.hasStage) throw new Error('Missing .workspace-stage container');
if (!res.hasHeader) throw new Error('Missing .workspace-header');
if (!res.titlebarHasContainer) throw new Error('#tab-bar-container must be inside .titlebar');
if (!res.containerHasTabs) throw new Error('#tab-bar must be inside #tab-bar-container');
if (!res.containerHasBtn) throw new Error('#btn-new-tab must be inside #tab-bar-container');
if (!res.hasViewport) throw new Error('Missing #main-viewport');
if (!res.tabsInHeader) throw new Error('#tab-bar must be inside .workspace-header');
if (!res.btnInHeader) throw new Error('#btn-new-tab must be inside .workspace-header');
if (res.hasOldHeader) throw new Error('.workspace-header should be removed (merged into titlebar)');
});
await runner.test('标签栏与终端窗口边缘完全像素级对齐 (100% Alignment)', async () => {
await runner.test('工作区展开时标签页和终端窗口左边像素级对齐', async () => {
const res = await win.webContents.executeJavaScript(`
(() => {
const hRect = document.querySelector('.workspace-header').getBoundingClientRect();
const tRect = document.getElementById('tab-bar-container').getBoundingClientRect();
const vRect = document.getElementById('main-viewport').getBoundingClientRect();
return {
headerLeft: hRect.left,
tabLeft: tRect.left,
viewportLeft: vRect.left,
headerWidth: hRect.width,
viewportWidth: vRect.width,
leftDiff: Math.abs(hRect.left - vRect.left),
widthDiff: Math.abs(hRect.width - vRect.width)
leftDiff: Math.abs(tRect.left - vRect.left)
};
})()
`);
if (res.leftDiff >= 1) {
throw new Error(`Left mismatch: header.left=${res.headerLeft}, viewport.left=${res.viewportLeft}`);
throw new Error(`Left mismatch: tabLeft=${res.tabLeft}, viewportLeft=${res.viewportLeft}`);
}
if (res.widthDiff >= 1) {
throw new Error(`Width mismatch: header.width=${res.headerWidth}, viewport.width=${res.viewportWidth}`);
});
await runner.test('工作区关闭时标签页位于左侧且不强求与终端左边对齐', async () => {
const res = await win.webContents.executeJavaScript(`
(() => {
// 折叠侧边栏
window.terminalApp?.workspaceManager?.setCollapsed(true);
const tRect = document.getElementById('tab-bar-container').getBoundingClientRect();
const titlebarLeft = document.getElementById('titlebar-left').getBoundingClientRect();
const vRect = document.getElementById('main-viewport').getBoundingClientRect();
// 恢复展开状态供后续测试
window.terminalApp?.workspaceManager?.setCollapsed(false);
return {
tabLeft: tRect.left,
controlsRight: titlebarLeft.right,
viewportLeft: vRect.left,
distFromControls: Math.abs(tRect.left - titlebarLeft.right)
};
})()
`);
if (res.distFromControls >= 2) {
throw new Error(`Tab bar not immediately beside titlebar-left controls when collapsed: tabLeft=${res.tabLeft}, controlsRight=${res.controlsRight}`);
}
});