From df32d7dbe5ac23c3562e38549d4523c92f8ad431 Mon Sep 17 00:00:00 2001 From: unidesk-code-queue-runner Date: Sat, 23 May 2026 15:33:17 +0000 Subject: [PATCH] fix: remove resource explorer from workbench --- scripts/dev-cloud-workbench-smoke.test.mjs | 89 +- scripts/src/dev-cloud-workbench-smoke-lib.mjs | 845 +++--------------- web/hwlab-cloud-web/app.mjs | 273 +----- web/hwlab-cloud-web/help.md | 8 +- web/hwlab-cloud-web/index.html | 43 - web/hwlab-cloud-web/scripts/check.mjs | 87 +- web/hwlab-cloud-web/styles.css | 215 +---- 7 files changed, 205 insertions(+), 1355 deletions(-) diff --git a/scripts/dev-cloud-workbench-smoke.test.mjs b/scripts/dev-cloud-workbench-smoke.test.mjs index 3ac58b9b..86f6a692 100644 --- a/scripts/dev-cloud-workbench-smoke.test.mjs +++ b/scripts/dev-cloud-workbench-smoke.test.mjs @@ -219,9 +219,9 @@ test("dedicated layout smoke CLI supports static build and DEV live shorthand", assert.equal(liveExplicit.urlExplicit, true); }); -test("source/default smoke covers #278 sidebar collapse static contract", () => { +test("source/default smoke covers #352 resource explorer removal contract", () => { const report = runDevCloudWorkbenchStaticSmoke(); - assert.equal(report.checks.find((check) => check.id === "feedback-278-sidebar-collapse-contract")?.status, "pass"); + assert.equal(report.checks.find((check) => check.id === "feedback-352-resource-explorer-removed")?.status, "pass"); }); test("source/default smoke covers #276 two-column wiring long-table contract", () => { @@ -693,7 +693,7 @@ test("Code Agent quick prompt fixture fills input, does not autosend writes, and assert.equal(layout.observations.some((item) => item.viewport.width === 390 && item.layoutOk), true); }); -test("layout smoke verifies desktop and mobile sidebar collapse geometry", async () => { +test("layout smoke verifies desktop and mobile default workbench geometry without resource explorer", async () => { const report = await runDevCloudWorkbenchLayoutSmoke(); if (report.status === "skip") { assert.match(report.summary, /Playwright is unavailable/u); @@ -718,17 +718,9 @@ test("layout smoke verifies desktop and mobile sidebar collapse geometry", async assert.equal(report.validationCommands.includes(command), true, `missing ${command}`); } for (const id of [ - "layout-desktop-expanded", - "layout-desktop-collapsed", - "layout-desktop-restored", - "layout-narrow-desktop-expanded", - "layout-narrow-desktop-collapsed", - "layout-narrow-desktop-restored", - "layout-mobile-collapsed", - "layout-mobile-drawer", - "layout-explorer-resize-desktop", - "layout-explorer-resize-narrow-desktop", - "layout-explorer-resize-mobile", + "layout-desktop-default", + "layout-narrow-desktop-default", + "layout-mobile-default", "layout-gate-desktop", "layout-gate-narrow-desktop", "layout-gate-mobile" @@ -750,60 +742,29 @@ test("layout smoke verifies desktop and mobile sidebar collapse geometry", async assert.equal(report.checks.find((check) => check.id === "layout-issue-287-future-hardware-status-tabs")?.status, "pass"); assert.equal(report.checks.find((check) => check.id === "layout-issue-288-future-single-table-gate")?.status, "skip"); - const desktopCollapsed = report.checks.find((check) => check.id === "layout-desktop-collapsed")?.observations; - assert.equal(desktopCollapsed.explorerCollapsed, true); - assert.equal(desktopCollapsed.toggle.ariaLabel, "展开左侧资源树"); - assert.equal(desktopCollapsed.toggle.text, "展开资源树"); - assert.equal(desktopCollapsed.collapsedWidthGain, true); - assert.equal(desktopCollapsed.keyTargetsReachable, true); - assert.equal(desktopCollapsed.wiring.longTableOk, true); - assert.equal(desktopCollapsed.wiring.noHorizontalScroll, true); - assert.equal(desktopCollapsed.wiring.headers.length, 2); - assert.deepEqual(desktopCollapsed.wiring.legacyHeaderHits, []); - assert.equal(desktopCollapsed.wiring.horizontalScroll.panelScrollWidth <= desktopCollapsed.wiring.horizontalScroll.panelClientWidth + 2, true); - assert.equal(desktopCollapsed.resizeHandle.ok, false); + const desktopDefault = report.checks.find((check) => check.id === "layout-desktop-default")?.observations; + assert.equal(desktopDefault.removedSelectorsAbsent, true); + assert.equal(desktopDefault.keyTargetsReachable, true); + assert.equal(desktopDefault.wiring.longTableOk, true); + assert.equal(desktopDefault.wiring.noHorizontalScroll, true); + assert.equal(desktopDefault.wiring.headers.length, 2); + assert.deepEqual(desktopDefault.wiring.legacyHeaderHits, []); + assert.equal(desktopDefault.wiring.horizontalScroll.panelScrollWidth <= desktopDefault.wiring.horizontalScroll.panelClientWidth + 2, true); + assert.equal(Object.hasOwn(desktopDefault.boxes.shell, "text"), false); + assert.equal(Object.hasOwn(desktopDefault.semanticOverlapChecks[0].boxes["#m3-control-form"], "text"), false); + assert.equal(desktopDefault.failures.length, 0); + assert.equal(desktopDefault.semanticOverlapChecks.every((check) => !check.overlaps), true); + assert.equal(desktopDefault.overflowChecks.every((check) => check.ok), true); - const mobileCollapsed = report.checks.find((check) => check.id === "layout-mobile-collapsed")?.observations; - assert.equal(mobileCollapsed.explorerCollapsed, true); - assert.equal(mobileCollapsed.defaultCollapsedMobile, true); - assert.equal(mobileCollapsed.keyTargetsReachable, true); - assert.equal(mobileCollapsed.wiring.longTableOk, true); - assert.equal(mobileCollapsed.wiring.noHorizontalScroll, true); - assert.equal(mobileCollapsed.resizeHandle.ok, false); - - const desktopExpanded = report.checks.find((check) => check.id === "layout-desktop-expanded")?.observations; - assert.equal(Object.hasOwn(desktopExpanded.boxes.shell, "text"), false); - assert.equal(Object.hasOwn(desktopExpanded.semanticOverlapChecks[0].boxes["#m3-control-form"], "text"), false); - assert.equal(desktopExpanded.failures.length, 0); - assert.equal(desktopExpanded.semanticOverlapChecks.every((check) => !check.overlaps), true); - assert.equal(desktopExpanded.overflowChecks.every((check) => check.ok), true); - assert.equal(desktopExpanded.resizeHandle.ok, true); - - const desktopResize = report.checks.find((check) => check.id === "layout-explorer-resize-desktop")?.observations; - assert.equal(desktopResize.pass, true); - assert.equal(desktopResize.before.handle.ariaLabel, "拖拽调整左侧资源树宽度"); - assert.equal(desktopResize.before.handle.role, "separator"); - assert.equal(Math.abs(desktopResize.afterDrag.explorerWidth - desktopResize.before.explorerWidth) >= 24, true); - assert.equal(desktopResize.afterEnd.explorerWidth, desktopResize.afterEnd.bounds.max); - assert.equal(desktopResize.afterHome.explorerWidth, desktopResize.afterHome.bounds.min); - assert.equal(desktopResize.collapsed.explorerCollapsed, true); - assert.equal(desktopResize.collapsed.handleTabIndex, -1); - assert.equal(desktopResize.restored.storage.version, 1); - assert.equal(desktopResize.restoredFromStorage.keyTargetsReachable, true); - - const narrowResize = report.checks.find((check) => check.id === "layout-explorer-resize-narrow-desktop")?.observations; - assert.equal(narrowResize.pass, true); - assert.equal(narrowResize.afterEnd.explorerWidth <= narrowResize.afterEnd.bounds.max, true); - assert.equal(narrowResize.restoredFromStorage.noHorizontalOverflow, true); - - const mobileResize = report.checks.find((check) => check.id === "layout-explorer-resize-mobile")?.observations; - assert.equal(mobileResize.pass, true); - assert.equal(mobileResize.handleDisplay, "none"); - assert.equal(mobileResize.handleTabIndex, -1); + const mobileDefault = report.checks.find((check) => check.id === "layout-mobile-default")?.observations; + assert.equal(mobileDefault.removedSelectorsAbsent, true); + assert.equal(mobileDefault.keyTargetsReachable, true); + assert.equal(mobileDefault.wiring.longTableOk, true); + assert.equal(mobileDefault.wiring.noHorizontalScroll, true); const compact = compactLayoutSmokeCliOutput(report); assert.equal(compact.status, "pass"); - assert.equal(compact.artifacts.screenshotCount, 21); + assert.equal(compact.artifacts.screenshotCount, 12); assert.equal(compact.failures.length, 0); assert.equal(compact.blockers.length, 0); assert.equal(JSON.stringify(compact).includes('"checks"'), false); diff --git a/scripts/src/dev-cloud-workbench-smoke-lib.mjs b/scripts/src/dev-cloud-workbench-smoke-lib.mjs index 905dbfa9..f2ed4158 100644 --- a/scripts/src/dev-cloud-workbench-smoke-lib.mjs +++ b/scripts/src/dev-cloud-workbench-smoke-lib.mjs @@ -209,8 +209,6 @@ const workbenchMarkers = Object.freeze([ "data-app-shell", "workbench-shell", "activity-rail", - "explorer", - "resource-tree", "center-workspace", "conversation-list", "agent-chat-status", @@ -440,7 +438,7 @@ function runStaticSmoke() { addCheck(checks, blockers, "feedback-118-complete-nav", hasCompletePrimaryNavigation(files.html), "Primary navigation uses complete Chinese labels and removes the left-rail trusted-records shortcut.", { blocker: "contract_blocker", - evidence: ["工作台", "内部复核", "使用说明", "资源树"] + evidence: ["工作台", "内部复核", "使用说明"] }); addCheck(checks, blockers, "feedback-288-gate-single-table", gateSingleTableContract(files), "Internal review /gate is a single read-only table backed by the live diagnostics aggregation route.", { @@ -489,23 +487,21 @@ function runStaticSmoke() { evidence: ["html/body overflow hidden", "workbench-shell 100vh/100dvh overflow hidden", ".view overflow auto"] }); - addCheck(checks, blockers, "mobile-workbench-layout-contract", hasMobileWorkbenchLayoutContract(files), "390x844 mobile layout keeps the default workbench reachable and moves the resource tree into a controlled drawer.", { + addCheck(checks, blockers, "mobile-workbench-layout-contract", hasMobileWorkbenchLayoutContract(files), "390x844 mobile layout keeps the default workbench reachable without a resource explorer drawer.", { blocker: "runtime_blocker", evidence: [ - "syncMobileExplorer collapses resource tree at <=860px", "mobile rail spans full height", "center workspace and right sidebar share the content column", - "resource explorer opens as a full-height drawer" + "resource explorer drawer selectors are absent" ] }); - addCheck(checks, blockers, "feedback-278-sidebar-collapse-contract", hasSidebarCollapseContract(files), "Left resource tree exposes explicit Chinese collapse/expand semantics and releases width for the Agent workspace plus M3 panel.", { + addCheck(checks, blockers, "feedback-352-resource-explorer-removed", hasResourceExplorerRemovalContract(files), "Default workbench removes the left resource explorer, quick actions, resource tree, capability cards, and explorer resize handle.", { blocker: "runtime_blocker", evidence: [ - "收起左侧资源树 / 展开左侧资源树 aria-label", - "收起资源树 / 展开资源树 visible button text", - "explorer-collapsed sets explorer column to 0", - "collapsed desktop center column minmax(420px, 1fr)", + "aside#resource-explorer absent", + "#explorer-resize absent", + "resource tree / quick actions / capability cards absent", "right side keeps a 560-740px M3 status workspace boundary through --right-width" ] }); @@ -1031,100 +1027,44 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) { const mobile = viewportResults.mobile; const checks = [ { - id: "layout-desktop-expanded", - status: desktop.expanded.pass ? "pass" : "blocked", - viewport: desktop.expanded.viewport, - summary: "Desktop 1366x768 expanded layout keeps M3 controls, Code Agent input, hardware panels, hit targets, overflow, and outer scroll in contract.", - observations: desktop.expanded + id: "layout-desktop-default", + status: desktop.default.pass ? "pass" : "blocked", + viewport: desktop.default.viewport, + summary: "Desktop 1366x768 default layout has no resource explorer DOM and keeps M3 controls, Code Agent input, hardware panels, hit targets, overflow, and outer scroll in contract.", + observations: desktop.default }, { - id: "layout-desktop-collapsed", - status: desktop.collapsed.pass ? "pass" : "blocked", - viewport: desktop.collapsed.viewport, - summary: "Desktop 1366x768 collapsed layout hides the resource tree, increases Agent/M3 usable width, and keeps key controls reachable.", - observations: desktop.collapsed - }, - { - id: "layout-desktop-restored", - status: desktop.restored.pass ? "pass" : "blocked", - viewport: desktop.restored.viewport, - summary: "Desktop expand action restores the left navigation/resource tree without layout overlap.", - observations: desktop.restored - }, - { - id: "layout-narrow-desktop-expanded", - status: narrowDesktop.expanded.pass ? "pass" : "blocked", - viewport: narrowDesktop.expanded.viewport, + id: "layout-narrow-desktop-default", + status: narrowDesktop.default.pass ? "pass" : "blocked", + viewport: narrowDesktop.default.viewport, summary: "Narrow desktop 1024x768 layout keeps the right hardware area below the workbench without covered controls or outer scroll.", - observations: narrowDesktop.expanded + observations: narrowDesktop.default }, { - id: "layout-narrow-desktop-collapsed", - status: narrowDesktop.collapsed.pass ? "pass" : "blocked", - viewport: narrowDesktop.collapsed.viewport, - summary: "Narrow desktop 1024x768 collapsed layout releases left-sidebar width without hiding M3 or Code Agent controls.", - observations: narrowDesktop.collapsed - }, - { - id: "layout-narrow-desktop-restored", - status: narrowDesktop.restored.pass ? "pass" : "blocked", - viewport: narrowDesktop.restored.viewport, - summary: "Narrow desktop 1024x768 restored layout returns the resource tree without incoherent overlap.", - observations: narrowDesktop.restored - }, - { - id: "layout-mobile-collapsed", - status: mobile.collapsed.pass ? "pass" : "blocked", - viewport: mobile.collapsed.viewport, - summary: "Mobile default layout keeps the resource tree collapsed and preserves Agent input plus M3 tabs/controls.", - observations: mobile.collapsed - }, - { - id: "layout-mobile-drawer", - status: mobile.drawer.pass ? "pass" : "blocked", - viewport: mobile.drawer.viewport, - summary: "Mobile drawer expansion makes resource controls reachable and can collapse back through the same accessible button.", - observations: mobile.drawer - }, - { - id: "layout-explorer-resize-desktop", - status: desktop.resize?.pass ? "pass" : "blocked", - viewport: desktop.resize?.viewport ?? desktop.expanded.viewport, - summary: "Desktop left resource tree resize supports pointer drag, keyboard bounds, clamping, localStorage v1 restore, and collapse/expand width preservation.", - observations: desktop.resize - }, - { - id: "layout-explorer-resize-narrow-desktop", - status: narrowDesktop.resize?.pass ? "pass" : "blocked", - viewport: narrowDesktop.resize?.viewport ?? narrowDesktop.expanded.viewport, - summary: "Narrow desktop left resource tree resize clamps against the stacked layout so M3 controls and Code Agent input remain reachable.", - observations: narrowDesktop.resize - }, - { - id: "layout-explorer-resize-mobile", - status: mobile.resize?.pass ? "pass" : "blocked", - viewport: mobile.resize?.viewport ?? mobile.collapsed.viewport, - summary: "Mobile layout disables the drag handle as a hit target and keeps the drawer/collapse flow reachable without horizontal overflow.", - observations: mobile.resize + id: "layout-mobile-default", + status: mobile.default.pass ? "pass" : "blocked", + viewport: mobile.default.viewport, + summary: "Mobile 390x844 default layout has no resource drawer and preserves Agent input plus M3 tabs/controls.", + observations: mobile.default }, { id: "layout-right-sidebar-resize-desktop", - status: desktop.rightResize?.pass ? "pass" : "blocked", - viewport: desktop.rightResize?.viewport ?? desktop.expanded.viewport, + status: desktop.rightResize.pass ? "pass" : "blocked", + viewport: desktop.rightResize.viewport ?? desktop.default.viewport, summary: "Desktop right hardware sidebar resize supports pointer drag, keyboard bounds, localStorage v1 restore, and keeps Code Agent plus M3 controls reachable.", observations: desktop.rightResize }, { id: "layout-right-sidebar-resize-narrow-desktop", - status: narrowDesktop.rightResize?.pass ? "pass" : "blocked", - viewport: narrowDesktop.rightResize?.viewport ?? narrowDesktop.expanded.viewport, + status: narrowDesktop.rightResize.pass ? "pass" : "blocked", + viewport: narrowDesktop.rightResize.viewport ?? narrowDesktop.default.viewport, summary: "Narrow desktop disables the right-sidebar drag handle while the stacked M3 hardware area remains reachable without outer page scroll.", observations: narrowDesktop.rightResize }, { id: "layout-right-sidebar-resize-mobile", - status: mobile.rightResize?.pass ? "pass" : "blocked", - viewport: mobile.rightResize?.viewport ?? mobile.collapsed.viewport, + status: mobile.rightResize.pass ? "pass" : "blocked", + viewport: mobile.rightResize.viewport ?? mobile.default.viewport, summary: "Mobile disables the right-sidebar drag handle so it cannot cover M3 tabs, Code Agent input, or the hardware status area.", observations: mobile.rightResize }, @@ -2004,7 +1944,7 @@ function textHasForbiddenTerm(text, term) { function hasCompletePrimaryNavigation(html) { const navLabelsPresent = ["工作台", "内部复核", "使用说明"].every((label) => html.includes(`>${label}<`)) && - />(?:资源树|收起资源树|展开资源树)<\/button>/u.test(html); + !/>(?:资源树|收起资源树|展开资源树)<\/button>/u.test(html); const legacyLabelsAbsent = incompletePrimaryNavLabels.every((label) => !html.includes(`>${label}<`)); const noLeftSideRecordsShortcut = !/data-side-tab-jump=["']records["']/u.test(html); return navLabelsPresent && legacyLabelsAbsent && noLeftSideRecordsShortcut; @@ -2181,62 +2121,67 @@ function hasScrollLockContract(styles) { /body\s*>\s*\[data-app-shell\]\s*\{[^\}]*min-height:\s*0;/su.test(styles) && /\.workbench-shell\s*\{[^\}]*height:\s*100(?:d)?vh;[^\}]*overflow:\s*hidden;/su.test(styles) && /\.view\s*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles) && - /\.(?:resource-tree|compact-list|conversation-list|task-list|hardware-list)[^\{]*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles) + /\.(?:compact-list|conversation-list|task-list|hardware-list)[^\{]*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles) ); } function hasMobileWorkbenchLayoutContract({ html, app, styles }) { return ( - /id=["']resource-explorer["']/u.test(html) && - /aria-controls=["']resource-explorer["']/u.test(html) && - /aria-expanded=["']true["']/u.test(html) && - /aria-pressed=["']false["']/u.test(html) && - /function\s+syncMobileExplorer\s*\(/u.test(app) && - /window\.matchMedia\(["']\(max-width:\s*860px\)["']\)/u.test(app) && - /setExplorerCollapsed\(true\)/u.test(app) && - /setExplorerCollapsed\(false\)/u.test(app) && - /function\s+collapseExplorerAfterMobileAction\s*\(/u.test(app) && - /aria-expanded/u.test(functionBody(app, "setExplorerCollapsed")) && - /aria-label/u.test(functionBody(app, "setExplorerCollapsed")) && - /展开资源树/u.test(functionBody(app, "setExplorerCollapsed")) && - /\.explorer-collapsed\s+\.explorer\s*\{[^\}]*pointer-events:\s*none;/su.test(styles) && + !/id=["']resource-explorer["']/u.test(html) && + !/id=["']explorer-toggle["']/u.test(html) && + !/id=["']explorer-resize["']/u.test(html) && + !/function\s+syncMobileExplorer\s*\(/u.test(app) && + !/function\s+setExplorerCollapsed\s*\(/u.test(app) && + !/function\s+collapseExplorerAfterMobileAction\s*\(/u.test(app) && /@media\s*\(max-width:\s*860px\)[\s\S]*?\.activity-rail\s*\{[\s\S]*?grid-row:\s*1\s*\/\s*3;/u.test(styles) && - /@media\s*\(max-width:\s*860px\)[\s\S]*?\.explorer\s*\{[\s\S]*?grid-row:\s*1\s*\/\s*3;[\s\S]*?z-index:\s*4;/u.test(styles) && /@media\s*\(max-width:\s*860px\)[\s\S]*?\.center-workspace\s*\{[\s\S]*?grid-row:\s*1;/u.test(styles) && /@media\s*\(max-width:\s*860px\)[\s\S]*?\.right-sidebar\s*\{[\s\S]*?grid-row:\s*2;/u.test(styles) && /@media\s*\(max-width:\s*520px\)[\s\S]*?\.command-bar\s*\{[\s\S]*?grid-template-columns:\s*minmax\(0,\s*1fr\)\s+auto\s+auto;/u.test(styles) ); } -function hasSidebarCollapseContract({ html, app, styles }) { - const setCollapsedBody = functionBody(app, "setExplorerCollapsed"); +function hasResourceExplorerRemovalContract({ html, app, styles }) { + const source = `${html}\n${app}\n${styles}`; + const removedSelectorsAbsent = [ + "resource-explorer", + "explorer-toggle", + "explorer-resize", + "resource-tree", + "explorer-capabilities", + "capability-count", + "tree-count", + "data-focus-command", + "quick-actions", + "tree-panel", + "explorer-collapsed", + "--explorer-width", + "--explorer-min-width", + "--explorer-max-width" + ].every((term) => !source.includes(term)); + const removedTextAbsent = [ + "资源浏览与硬件资源树", + "只读工作区", + "常用能力", + "收起资源树", + "展开资源树", + "拖拽调整左侧资源树宽度", + "资源查看", + "查看接线", + "查看状态", + "编写任务" + ].every((term) => !source.includes(term)); return ( - /id=["']explorer-toggle["']/u.test(html) && - /aria-label=["']收起左侧资源树["']/u.test(html) && - /title=["']收起左侧资源树,给工作区更多宽度["']/u.test(html) && - />收起资源树<\/button>/u.test(html) && - /id=["']explorer-resize["']/u.test(html) && - /role=["']separator["']/u.test(html) && - /aria-label=["']拖拽调整左侧资源树宽度["']/u.test(html) && - /aria-valuemin=["']220["']/u.test(html) && - /aria-valuemax=["']420["']/u.test(html) && - /aria-valuenow=["']292["']/u.test(html) && + removedSelectorsAbsent && + removedTextAbsent && /LAYOUT_STORAGE_KEY\s*=\s*["']hwlab\.workbench\.layout\.v1["']/u.test(app) && - /function\s+initExplorerResize\s*\(/u.test(app) && - /function\s+clampExplorerWidth\s*\(/u.test(app) && /setPointerCapture/u.test(app) && /localStorage\?\.setItem\(\s*LAYOUT_STORAGE_KEY/u.test(app) && /event\.key === "Home"/u.test(app) && /event\.key === "End"/u.test(app) && /ArrowLeft/u.test(app) && /ArrowRight/u.test(app) && - /setAttribute\("aria-hidden", resizeDisabled \? "true" : "false"\)/u.test(setCollapsedBody) && - /setAttribute\("aria-label", collapsed \? "展开左侧资源树" : "收起左侧资源树"\)/u.test(setCollapsedBody) && - /textContent = collapsed \? "展开资源树" : "收起资源树"/u.test(setCollapsedBody) && - /title = collapsed \? "展开左侧资源树,恢复导航和硬件资源" : "收起左侧资源树,给工作区更多宽度"/u.test(setCollapsedBody) && - /--explorer-width:\s*292px/u.test(styles) && - /--explorer-min-width:\s*220px/u.test(styles) && - /--explorer-max-width:\s*420px/u.test(styles) && + /\.workbench-shell\s*\{[^}]*grid-template-columns:\s*var\(--rail-width\)\s+minmax\(420px,\s*1fr\)\s+var\(--right-width-expanded\);/su.test(styles) && + /@media\s*\(max-width:\s*1240px\)[\s\S]*?\.workbench-shell\s*\{[\s\S]*?grid-template-columns:\s*var\(--rail-width\)\s+minmax\(0,\s*1fr\);/u.test(styles) && /id=["']hardware-sidebar["']/u.test(html) && /id=["']right-sidebar-resize["']/u.test(html) && /aria-label=["']拖拽调整右侧硬件状态栏宽度["']/u.test(html) && @@ -2256,12 +2201,8 @@ function hasSidebarCollapseContract({ html, app, styles }) { /--right-sidebar-max-width:\s*760px/u.test(styles) && /--right-width:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/u.test(styles) && /--right-width-expanded:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/u.test(styles) && - /\.explorer-resize\s*\{[^}]*cursor:\s*col-resize;/su.test(styles) && /\.right-sidebar-resize\s*\{[^}]*cursor:\s*col-resize;/su.test(styles) && - /\.explorer-collapsed\s+\.explorer-resize\s*\{[^}]*display:\s*none;/su.test(styles) && - /@media\s*\(max-width:\s*860px\)[\s\S]*?\.explorer-resize\s*\{[\s\S]*?display:\s*none;/u.test(styles) && /@media\s*\(max-width:\s*1240px\)[\s\S]*?\.right-sidebar-resize\s*\{[\s\S]*?display:\s*none;/u.test(styles) && - /\.explorer-collapsed\s*\{[^}]*--explorer-width:\s*0px;[^}]*grid-template-columns:\s*var\(--rail-width\)\s+0\s+minmax\(420px,\s*1fr\)\s+var\(--right-width\);/su.test(styles) && /\.side-panel\s*\{[^}]*overflow:\s*auto;[^}]*overscroll-behavior:\s*contain;/su.test(styles) ); } @@ -2269,7 +2210,7 @@ function hasSidebarCollapseContract({ html, app, styles }) { function hasStableRouteControls({ html, styles }) { return ( ["工作台", "内部复核", "使用说明", "控制", "接线", "可信记录"].every((label) => html.includes(`>${label}<`)) && - />(?:资源树|收起资源树|展开资源树)<\/button>/u.test(html) && + !/>(?:资源树|收起资源树|展开资源树)<\/button>/u.test(html) && /\.rail-button\s*\{[^}]*min-width:\s*0;[^}]*min-height:\s*36px;[^}]*text-align:\s*center;[^}]*word-break:\s*keep-all;/su.test(styles) && /@media\s*\(max-width:\s*860px\)[\s\S]*?\.rail-button\s*\{[\s\S]*?min-height:\s*42px;/u.test(styles) && /\.side-tab\s*\{[^}]*min-width:\s*0;[^}]*min-height:\s*34px;[^}]*overflow-wrap:\s*anywhere;[^}]*word-break:\s*keep-all;/su.test(styles) && @@ -3031,9 +2972,9 @@ async function inspectLiveDom(url, options = {}) { firstViewportForbiddenPresent: forbiddenTerms.filter((term) => textHasForbiddenTerm(firstViewportText, term)), firstViewportTextSample: firstViewportText.slice(0, 400), labelsPresent: ["硬件资源", "Agent 对话", "工作清单", "可信记录", "使用说明"].every((label) => text.includes(label)), - navLabelsPresent: ["工作台", "内部复核", "使用说明", "资源树"].every((label) => + navLabelsPresent: ["工作台", "内部复核", "使用说明"].every((label) => [...document.querySelectorAll(".activity-rail button")].some((button) => button.textContent?.trim() === label) - ), + ) && ![...document.querySelectorAll(".activity-rail button")].some((button) => /资源树|收起资源树|展开资源树/u.test(button.textContent?.trim() ?? "")), defaultBackstageHidden: !/(Gate|诊断|验收|BLOCKED|M0-M5|执行轨迹)/u.test(document.querySelector(".center-workspace")?.innerText ?? ""), gateRouteAvailable: [...document.querySelectorAll("[data-route='gate']")].some((button) => button.textContent?.trim() === "内部复核"), wiringLayout: document.querySelector(".wiring-table-wrap")?.getAttribute("data-wiring-layout") ?? "", @@ -4181,7 +4122,7 @@ export async function runDevCloudWorkbenchMobileSmoke() { isMobile: true }); await page.goto(server.url, { waitUntil: "networkidle", timeout: 15000 }); - const closed = await inspectMobileWorkbench(page, { explorerOpen: false }); + const closed = await inspectMobileWorkbench(page); const workspaceScroll = await inspectWorkbenchScrollContract(page, { panelSelector: "#conversation-list" }); await page.click('[data-route="help"]'); await page.waitForFunction(() => document.querySelector("#help-content")?.dataset.helpState === "ready", null, { timeout: 8000 }); @@ -4194,10 +4135,6 @@ export async function runDevCloudWorkbenchMobileSmoke() { const directHelp = await inspectHelpMarkdownRoute(page); await page.click('[data-route="workspace"]'); await page.waitForFunction(() => document.querySelector('[data-view="workspace"]')?.hidden === false, null, { timeout: 8000 }); - await page.click("#explorer-toggle"); - const open = await inspectMobileWorkbench(page, { explorerOpen: true }); - await page.click('[data-side-tab-jump="wiring"]'); - const afterQuickAction = await inspectMobileWorkbench(page, { explorerOpen: false }); const checks = [ { id: "mobile-default-workbench", @@ -4208,7 +4145,7 @@ export async function runDevCloudWorkbenchMobileSmoke() { workspaceHidden: closed.workspaceHidden, gateHidden: closed.gateHidden, helpHidden: closed.helpHidden, - explorerCollapsed: closed.explorerCollapsed, + removedSelectors: closed.removedSelectors, visibleText: closed.visibleText } }, @@ -4246,30 +4183,18 @@ export async function runDevCloudWorkbenchMobileSmoke() { { id: "mobile-default-hit-test", status: closed.primaryControlsReachable ? "pass" : "blocked", - summary: "Primary workbench controls are center-click reachable with the resource tree closed.", + summary: "Primary workbench controls are center-click reachable without a resource tree drawer.", observations: { reachable: closed.reachable, blocked: closed.blocked } }, { - id: "mobile-resource-drawer-hit-test", - status: open.resourceControlsReachable ? "pass" : "blocked", - summary: "Opened resource drawer keeps quick actions and resource rows center-click reachable.", + id: "mobile-resource-explorer-removed", + status: closed.resourceExplorerRemoved ? "pass" : "blocked", + summary: "390x844 default DOM has no resource explorer, explorer resize handle, quick actions, or resource tree.", observations: { - drawer: open.drawer, - reachable: open.reachable, - blocked: open.blocked - } - }, - { - id: "mobile-resource-action-collapse", - status: afterQuickAction.resourceActionCollapsed ? "pass" : "blocked", - summary: "Mobile resource quick actions collapse the drawer so the target workspace panel remains reachable.", - observations: { - explorerCollapsed: afterQuickAction.explorerCollapsed, - wiringSelected: afterQuickAction.wiringSelected, - tabHit: afterQuickAction.reachable.find((target) => target.label === "接线") + removedSelectors: closed.removedSelectors } } ]; @@ -5391,8 +5316,8 @@ function contentTypeFor(filePath) { return "application/octet-stream"; } -async function inspectMobileWorkbench(page, { explorerOpen }) { - return page.evaluate(async ({ explorerOpen }) => { +async function inspectMobileWorkbench(page) { + return page.evaluate(async () => { function visibleTextFor(selector) { const element = document.querySelector(selector); return element?.textContent?.replace(/\s+/gu, " ").trim() ?? ""; @@ -5435,22 +5360,10 @@ async function inspectMobileWorkbench(page, { explorerOpen }) { } return inspectTarget(element, label); } - async function inspectScrollableTargets(selector) { - const targets = [...document.querySelectorAll(selector)]; - const results = []; - for (const target of targets) { - target.scrollIntoView({ block: "center", inline: "nearest" }); - await new Promise((resolve) => requestAnimationFrame(resolve)); - results.push(inspectTarget(target)); - } - return results; - } - const defaultTargets = [ inspectSelector('[data-route="workspace"]', "工作台"), inspectSelector('[data-route="gate"]', "内部复核"), inspectSelector('[data-route="help"]', "使用说明"), - inspectSelector("#explorer-toggle", "资源树"), inspectSelector("#command-input", "Agent 输入"), inspectSelector("#command-send", "发送"), inspectSelector("#command-clear", "清空"), @@ -5458,33 +5371,31 @@ async function inspectMobileWorkbench(page, { explorerOpen }) { inspectSelector("#tab-wiring", "接线"), inspectSelector("#tab-records", "可信记录") ]; - const drawerTargets = explorerOpen - ? [ - ...[...document.querySelectorAll(".quick-actions button")].map((element) => inspectTarget(element)), - ...(await inspectScrollableTargets("#resource-tree summary, #resource-tree .tree-row")) - ] - : []; - const reachable = explorerOpen ? drawerTargets : defaultTargets; + const reachable = defaultTargets; const blocked = reachable.filter((target) => !target.ok); const text = document.body.textContent ?? ""; const workspace = document.querySelector('[data-view="workspace"]'); const gate = document.querySelector('[data-view="gate"]'); const help = document.querySelector('[data-view="help"]'); - const shell = document.querySelector("[data-app-shell]"); - const drawer = boxFor("#resource-explorer"); + const removedSelectors = { + resourceExplorer: document.querySelector("#resource-explorer") === null, + explorerResize: document.querySelector("#explorer-resize") === null, + explorerToggle: document.querySelector("#explorer-toggle") === null, + quickActions: document.querySelector(".quick-actions") === null, + resourceTree: document.querySelector("#resource-tree") === null + }; return { title: document.title, workspaceHidden: workspace ? workspace.hidden : null, gateHidden: gate ? gate.hidden : null, helpHidden: help ? help.hidden : null, - explorerCollapsed: shell?.classList.contains("explorer-collapsed") ?? null, + removedSelectors, visibleText: { bodyHasWorkbench: text.includes("用户工作台") && text.includes("Agent 对话"), gateIsSecondaryLabel: text.includes("内部复核"), helpIsSecondaryLabel: text.includes("使用说明"), visibleWorkspaceText: visibleTextFor(".center-workspace").slice(0, 160) }, - drawer, defaultWorkbenchReachable: document.title === "HWLAB 云工作台" && workspace?.hidden === false && @@ -5495,16 +5406,12 @@ async function inspectMobileWorkbench(page, { explorerOpen }) { text.includes("内部复核") && text.includes("使用说明"), primaryControlsReachable: defaultTargets.every((target) => target.ok), - resourceControlsReachable: drawerTargets.length >= 8 && drawerTargets.every((target) => target.ok), - resourceActionCollapsed: - !explorerOpen && - shell?.classList.contains("explorer-collapsed") === true && - document.querySelector("#tab-wiring")?.getAttribute("aria-selected") === "true", + resourceExplorerRemoved: Object.values(removedSelectors).every(Boolean), wiringSelected: document.querySelector("#tab-wiring")?.getAttribute("aria-selected") === "true", reachable, blocked }; - }, { explorerOpen }); + }); } async function inspectWorkbenchLayoutViewport(browser, url, viewport, options = {}) { @@ -5516,70 +5423,19 @@ async function inspectWorkbenchLayoutViewport(browser, url, viewport, options = const artifacts = { screenshots: [] }; try { await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15000 }); - await page.locator("#explorer-toggle").waitFor({ state: "visible", timeout: 12000 }); + await page.locator("#command-input").waitFor({ state: "visible", timeout: 12000 }); await page.waitForTimeout(150); - if (viewport.isMobile) { - const collapsed = await inspectLayoutState(page, { - mode: "mobile-collapsed", - viewport, - compareTo: null - }); - artifacts.screenshots.push(...await captureLayoutScreenshots(page, options.artifactRoot, viewport, "mobile-collapsed")); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => !document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const drawer = await inspectLayoutState(page, { - mode: "mobile-drawer", - viewport, - compareTo: collapsed - }); - artifacts.screenshots.push(...await captureLayoutScreenshots(page, options.artifactRoot, viewport, "mobile-drawer")); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const recollapsed = await inspectLayoutState(page, { - mode: "mobile-recollapsed", - viewport, - compareTo: collapsed - }); - drawer.recollapsed = { - explorerCollapsed: recollapsed.explorerCollapsed, - toggleText: recollapsed.toggle.text, - toggleAriaLabel: recollapsed.toggle.ariaLabel, - keyTargetsReachable: recollapsed.keyTargetsReachable - }; - drawer.pass = drawer.pass && recollapsed.explorerCollapsed === true && recollapsed.keyTargetsReachable; - const resize = await inspectExplorerResizeMobile(page, { viewport }); - const rightResize = await inspectRightSidebarResizeDisabled(page, { viewport }); - const gate = await inspectWorkbenchGateLayout(browser, url, viewport, options); - return { collapsed, drawer, resize, rightResize, gate, artifacts }; - } - - const expanded = await inspectLayoutState(page, { - mode: "desktop-expanded", + const defaultLayout = await inspectLayoutState(page, { + mode: viewport.isMobile ? "mobile-default" : viewport.width <= 1240 ? "narrow-desktop-default" : "desktop-default", viewport, compareTo: null }); - artifacts.screenshots.push(...await captureLayoutScreenshots(page, options.artifactRoot, viewport, "desktop-expanded")); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const collapsed = await inspectLayoutState(page, { - mode: "desktop-collapsed", - viewport, - compareTo: expanded - }); - artifacts.screenshots.push(...await captureLayoutScreenshots(page, options.artifactRoot, viewport, "desktop-collapsed")); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => !document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const restored = await inspectLayoutState(page, { - mode: "desktop-restored", - viewport, - compareTo: expanded - }); - const resize = await inspectExplorerResizeDesktop(page, { viewport }); + artifacts.screenshots.push(...await captureLayoutScreenshots(page, options.artifactRoot, viewport, defaultLayout.mode)); const rightResize = viewport.width > 1240 ? await inspectRightSidebarResizeDesktop(page, { viewport }) : await inspectRightSidebarResizeDisabled(page, { viewport }); const gate = await inspectWorkbenchGateLayout(browser, url, viewport, options); - return { expanded, collapsed, restored, resize, rightResize, gate, artifacts }; + return { default: defaultLayout, rightResize, gate, artifacts }; } finally { await page.close(); } @@ -5605,295 +5461,6 @@ async function inspectWorkbenchGateLayout(browser, url, viewport, options = {}) } } -async function inspectExplorerResizeDesktop(page, { viewport }) { - const before = await page.evaluate(() => { - const shell = document.querySelector("[data-app-shell]"); - const explorer = document.querySelector("#resource-explorer"); - const center = document.querySelector(".center-workspace"); - const right = document.querySelector(".right-sidebar"); - const handle = document.querySelector("#explorer-resize"); - return { - explorerWidth: explorer?.getBoundingClientRect().width ?? 0, - centerWidth: center?.getBoundingClientRect().width ?? 0, - rightWidth: right?.getBoundingClientRect().width ?? 0, - cssWidth: Number.parseFloat(getComputedStyle(shell).getPropertyValue("--explorer-width")), - storage: localStorage.getItem("hwlab.workbench.layout.v1"), - handle: { - ariaLabel: handle?.getAttribute("aria-label") ?? "", - title: handle?.getAttribute("title") ?? "", - role: handle?.getAttribute("role") ?? "", - tabIndex: handle?.tabIndex ?? null, - valueMin: handle?.getAttribute("aria-valuemin") ?? "", - valueMax: handle?.getAttribute("aria-valuemax") ?? "", - valueNow: handle?.getAttribute("aria-valuenow") ?? "", - valueText: handle?.getAttribute("aria-valuetext") ?? "" - } - }; - }); - const min = Number(before.handle.valueMin); - const max = Number(before.handle.valueMax); - const growTarget = Math.min(max, before.explorerWidth + 72); - const shrinkTarget = Math.max(min, before.explorerWidth - 48); - const dragTarget = growTarget - before.explorerWidth >= 32 ? growTarget : shrinkTarget; - await dragExplorerResizeHandle(page, dragTarget); - const afterDrag = await explorerResizeMetrics(page); - await page.locator("#explorer-resize").press("ArrowLeft"); - const afterArrowLeft = await explorerResizeMetrics(page); - await page.locator("#explorer-resize").press("End"); - const afterEnd = await explorerResizeMetrics(page); - await page.locator("#explorer-resize").press("Home"); - const afterHome = await explorerResizeMetrics(page); - await page.locator("#explorer-resize").press("ArrowRight"); - const afterArrowRight = await explorerResizeMetrics(page); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const collapsed = await explorerResizeMetrics(page); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => !document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const restored = await explorerResizeMetrics(page); - await page.reload({ waitUntil: "domcontentloaded", timeout: 15000 }); - await page.locator("#explorer-toggle").waitFor({ state: "visible", timeout: 12000 }); - await page.waitForTimeout(150); - const restoredFromStorage = await explorerResizeMetrics(page); - const expectedArrow = Math.min(afterHome.bounds.max, afterHome.bounds.min + 16); - const pass = - before.handle.role === "separator" && - before.handle.ariaLabel === "拖拽调整左侧资源树宽度" && - before.handle.title.includes("左右方向键") && - before.handle.tabIndex === 0 && - Number.isFinite(min) && - Number.isFinite(max) && - max >= min && - Math.abs(afterDrag.explorerWidth - before.explorerWidth) >= 24 && - afterDrag.explorerWidth >= min && - afterDrag.explorerWidth <= max && - afterArrowLeft.explorerWidth <= afterDrag.explorerWidth - 12 && - Math.abs(afterEnd.explorerWidth - afterEnd.bounds.max) <= 2 && - Math.abs(afterHome.explorerWidth - afterHome.bounds.min) <= 2 && - Math.abs(afterArrowRight.explorerWidth - expectedArrow) <= 2 && - collapsed.explorerCollapsed === true && - collapsed.explorerWidth <= 1 && - collapsed.handleTabIndex === -1 && - Math.abs(restored.explorerWidth - afterArrowRight.explorerWidth) <= 2 && - restored.storage?.version === 1 && - restored.storage?.explorerWidth === afterArrowRight.explorerWidth && - Math.abs(restoredFromStorage.explorerWidth - afterArrowRight.explorerWidth) <= 2 && - restoredFromStorage.keyTargetsReachable && - restoredFromStorage.rootScrollLocked && - restoredFromStorage.noHorizontalOverflow; - return { - viewport, - pass, - targetWidth: dragTarget, - before, - afterDrag, - afterArrowLeft, - afterEnd, - afterHome, - afterArrowRight, - collapsed, - restored, - restoredFromStorage - }; -} - -async function dragExplorerResizeHandle(page, targetWidth) { - const start = await page.evaluate(() => { - const explorer = document.querySelector("#resource-explorer"); - const handle = document.querySelector("#explorer-resize"); - const explorerBox = explorer.getBoundingClientRect(); - const handleBox = handle.getBoundingClientRect(); - return { - startX: handleBox.left + handleBox.width / 2, - startY: handleBox.top + Math.min(80, handleBox.height / 2), - currentWidth: explorerBox.width - }; - }); - const targetX = start.startX + targetWidth - start.currentWidth; - await page.mouse.move(start.startX, start.startY); - await page.mouse.down(); - await page.mouse.move(targetX, start.startY, { steps: 8 }); - await page.mouse.up(); -} - -async function explorerResizeMetrics(page) { - return page.evaluate(async () => { - const shell = document.querySelector("[data-app-shell]"); - const explorer = document.querySelector("#resource-explorer"); - const center = document.querySelector(".center-workspace"); - const right = document.querySelector(".right-sidebar"); - const handle = document.querySelector("#explorer-resize"); - const ownsHit = (element, hit) => hit === element || element.contains(hit); - const hitLabel = (hit) => hit ? `${hit.tagName.toLowerCase()}${hit.id ? `#${hit.id}` : ""}${hit.className ? `.${String(hit.className).trim().replace(/\s+/gu, ".")}` : ""}` : "none"; - const nearestScrollableAncestor = (element) => { - let current = element.parentElement; - while (current && current !== document.documentElement) { - const style = getComputedStyle(current); - const scrollable = current.scrollHeight > current.clientHeight + 2 && !["visible", "clip"].includes(style.overflowY); - if (scrollable) return current; - current = current.parentElement; - } - return null; - }; - const scrollIntoNearestContainer = async (element) => { - const container = nearestScrollableAncestor(element); - if (!container) { - element.scrollIntoView({ block: "center", inline: "nearest" }); - await new Promise((resolve) => requestAnimationFrame(resolve)); - return null; - } - const elementBox = element.getBoundingClientRect(); - const containerBox = container.getBoundingClientRect(); - const inset = Math.max(6, Math.min(24, (container.clientHeight - Math.min(elementBox.height, container.clientHeight)) / 2)); - container.scrollTop += elementBox.top - containerBox.top - inset; - await new Promise((resolve) => requestAnimationFrame(resolve)); - return container; - }; - const clipBoxForElement = (element) => { - let visible = { - left: 0, - top: 0, - right: window.innerWidth, - bottom: window.innerHeight - }; - let current = element.parentElement; - while (current && current !== document.documentElement) { - const style = getComputedStyle(current); - if (!["visible", "clip"].includes(style.overflow) || !["visible", "clip"].includes(style.overflowY) || !["visible", "clip"].includes(style.overflowX)) { - const box = current.getBoundingClientRect(); - visible = { - left: Math.max(visible.left, box.left), - top: Math.max(visible.top, box.top), - right: Math.min(visible.right, box.right), - bottom: Math.min(visible.bottom, box.bottom) - }; - } - current = current.parentElement; - } - return visible; - }; - const targetSelectors = [ - "#command-input", - "#command-send", - "#m3-write-do", - "#m3-read-di", - "[data-hardware-tab='overview']", - "[data-hardware-tab='box1']", - "[data-hardware-tab='box2']", - "[data-hardware-tab='patch']" - ]; - const keyTargets = []; - for (const selector of targetSelectors) { - const element = document.querySelector(selector); - if (!element) { - keyTargets.push({ selector, ok: false, missing: true }); - continue; - } - const scrollContainer = await scrollIntoNearestContainer(element); - const box = element.getBoundingClientRect(); - const clipBox = clipBoxForElement(element); - const visibleBox = { - left: Math.max(box.left, clipBox.left), - top: Math.max(box.top, clipBox.top), - right: Math.min(box.right, clipBox.right), - bottom: Math.min(box.bottom, clipBox.bottom) - }; - const visibleWidth = Math.max(0, visibleBox.right - visibleBox.left); - const visibleHeight = Math.max(0, visibleBox.bottom - visibleBox.top); - const x = visibleBox.left + visibleWidth / 2; - const y = visibleBox.top + visibleHeight / 2; - const stack = document.elementsFromPoint(x, y); - keyTargets.push({ - selector, - ok: - box.width > 0 && - box.height > 0 && - visibleWidth > 0 && - visibleHeight > 0 && - x >= 0 && - x <= window.innerWidth && - y >= 0 && - y <= window.innerHeight && - stack.some((candidate) => ownsHit(element, candidate)), - hit: hitLabel(stack[0] ?? null), - center: { x, y }, - visibleBox, - scrollContainer: scrollContainer ? scrollContainer.id ? `#${scrollContainer.id}` : scrollContainer.className ? `.${String(scrollContainer.className).trim().replace(/\s+/gu, ".")}` : scrollContainer.tagName.toLowerCase() : null - }); - } - let storage = null; - try { - storage = JSON.parse(localStorage.getItem("hwlab.workbench.layout.v1") ?? "null"); - } catch { - storage = "parse_failed"; - } - return { - explorerCollapsed: shell?.classList.contains("explorer-collapsed") === true, - explorerWidth: explorer?.getBoundingClientRect().width ?? 0, - centerWidth: center?.getBoundingClientRect().width ?? 0, - rightWidth: right?.getBoundingClientRect().width ?? 0, - cssWidth: Number.parseFloat(getComputedStyle(shell).getPropertyValue("--explorer-width")), - bounds: { - min: Number(handle?.getAttribute("aria-valuemin")), - max: Number(handle?.getAttribute("aria-valuemax")) - }, - handleTabIndex: handle?.tabIndex ?? null, - handleAriaDisabled: handle?.getAttribute("aria-disabled") ?? "", - handleAriaHidden: handle?.getAttribute("aria-hidden") ?? "", - handleAriaValueNow: Number(handle?.getAttribute("aria-valuenow")), - handleAriaValueText: handle?.getAttribute("aria-valuetext") ?? "", - storage, - keyTargets, - keyTargetsReachable: keyTargets.every((target) => target.ok), - noHorizontalOverflow: - document.documentElement.scrollWidth <= document.documentElement.clientWidth + 2 && - document.body.scrollWidth <= document.body.clientWidth + 2 && - (!right || right.scrollWidth <= right.clientWidth + 2), - rootScrollLocked: - getComputedStyle(document.documentElement).overflow === "hidden" && - getComputedStyle(document.body).overflow === "hidden" && - document.documentElement.scrollHeight <= document.documentElement.clientHeight + 2 && - document.body.scrollHeight <= document.body.clientHeight + 2 - }; - }); -} - -async function inspectExplorerResizeMobile(page, { viewport }) { - const observation = await page.evaluate(() => { - const handle = document.querySelector("#explorer-resize"); - const box = handle?.getBoundingClientRect(); - const style = handle ? getComputedStyle(handle) : null; - const shell = document.querySelector("[data-app-shell]"); - const right = document.querySelector(".right-sidebar"); - return { - viewport: { width: window.innerWidth, height: window.innerHeight }, - explorerCollapsed: shell?.classList.contains("explorer-collapsed") === true, - handlePresent: Boolean(handle), - handleDisplay: style?.display ?? "missing", - handleWidth: box?.width ?? 0, - handleTabIndex: handle?.tabIndex ?? null, - handleAriaDisabled: handle?.getAttribute("aria-disabled") ?? "", - handleAriaHidden: handle?.getAttribute("aria-hidden") ?? "", - noHorizontalOverflow: - document.documentElement.scrollWidth <= document.documentElement.clientWidth + 2 && - document.body.scrollWidth <= document.body.clientWidth + 2 && - (!right || right.scrollWidth <= right.clientWidth + 2) - }; - }); - return { - viewport, - pass: - observation.explorerCollapsed === true && - observation.handlePresent && - observation.handleDisplay === "none" && - observation.handleTabIndex === -1 && - observation.handleAriaDisabled === "true" && - observation.handleAriaHidden === "true" && - observation.noHorizontalOverflow, - ...observation - }; -} - async function inspectRightSidebarResizeDesktop(page, { viewport }) { const before = await page.evaluate(() => { const shell = document.querySelector("[data-app-shell]"); @@ -5932,12 +5499,6 @@ async function inspectRightSidebarResizeDesktop(page, { viewport }) { const afterHome = await rightSidebarResizeMetrics(page); await page.locator("#right-sidebar-resize").press("ArrowLeft"); const afterArrowLeft = await rightSidebarResizeMetrics(page); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const collapsedLeft = await rightSidebarResizeMetrics(page); - await page.locator("#explorer-toggle").click(); - await page.waitForFunction(() => !document.querySelector("[data-app-shell]")?.classList.contains("explorer-collapsed"), null, { timeout: 8000 }); - const restoredLeft = await rightSidebarResizeMetrics(page); await page.reload({ waitUntil: "domcontentloaded", timeout: 15000 }); await page.locator("#right-sidebar-resize").waitFor({ state: "visible", timeout: 12000 }); await page.waitForTimeout(150); @@ -5958,12 +5519,8 @@ async function inspectRightSidebarResizeDesktop(page, { viewport }) { Math.abs(afterEnd.rightWidth - afterEnd.bounds.max) <= 2 && Math.abs(afterHome.rightWidth - afterHome.bounds.min) <= 2 && Math.abs(afterArrowLeft.rightWidth - expectedArrowLeft) <= 2 && - collapsedLeft.leftExplorerCollapsed === true && - collapsedLeft.handleTabIndex === 0 && - collapsedLeft.keyTargetsReachable && - Math.abs(restoredLeft.rightWidth - afterArrowLeft.rightWidth) <= 2 && - restoredLeft.storage?.version === 1 && - restoredLeft.storage?.rightSidebarWidth === afterArrowLeft.rightWidth && + afterArrowLeft.storage?.version === 1 && + afterArrowLeft.storage?.rightSidebarWidth === afterArrowLeft.rightWidth && Math.abs(restoredFromStorage.rightWidth - afterArrowLeft.rightWidth) <= 2 && restoredFromStorage.keyTargetsReachable && restoredFromStorage.rootScrollLocked && @@ -5978,8 +5535,6 @@ async function inspectRightSidebarResizeDesktop(page, { viewport }) { afterEnd, afterHome, afterArrowLeft, - collapsedLeft, - restoredLeft, restoredFromStorage }; } @@ -6006,7 +5561,6 @@ async function dragRightSidebarResizeHandle(page, targetWidth) { async function rightSidebarResizeMetrics(page) { return page.evaluate(async () => { const shell = document.querySelector("[data-app-shell]"); - const explorer = document.querySelector("#resource-explorer"); const center = document.querySelector(".center-workspace"); const right = document.querySelector("#hardware-sidebar"); const handle = document.querySelector("#right-sidebar-resize"); @@ -6118,8 +5672,9 @@ async function rightSidebarResizeMetrics(page) { storage = "parse_failed"; } return { - leftExplorerCollapsed: shell?.classList.contains("explorer-collapsed") === true, - explorerWidth: explorer?.getBoundingClientRect().width ?? 0, + resourceExplorerPresent: Boolean(document.querySelector("#resource-explorer")), + explorerResizePresent: Boolean(document.querySelector("#explorer-resize")), + explorerTogglePresent: Boolean(document.querySelector("#explorer-toggle")), centerWidth: center?.getBoundingClientRect().width ?? 0, rightWidth: right?.getBoundingClientRect().width ?? 0, cssWidth: Number.parseFloat(getComputedStyle(shell).getPropertyValue("--right-sidebar-width")), @@ -6199,7 +5754,6 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { const failures = []; const skip = []; const selectorLabels = [ - ["#explorer-toggle", "资源树折叠按钮"], ["#command-input", "Code Agent 输入"], ["#command-send", "发送"], ["#m3-gateway-select", "M3 Gateway select"], @@ -6240,11 +5794,6 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { ["#command-form", ".right-sidebar", "Code Agent command form vs right sidebar"] ]; const wiringLegacyHeaders = ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据"]; - const drawerSelectorLabels = [ - [".quick-actions button", "资源树快捷操作"], - ["#resource-tree summary", "资源树分组"], - ["#resource-tree .tree-row", "资源树资源行"] - ]; const boxForElement = (element) => { if (!element) return null; const box = element.getBoundingClientRect(); @@ -6397,13 +5946,10 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { await new Promise((resolve) => requestAnimationFrame(resolve)); const shell = document.querySelector("[data-app-shell]"); - const toggleElement = document.querySelector("#explorer-toggle"); const isDesktop = viewport.width >= 861; - const isMobile = !isDesktop; const boxes = { shell: boxFor("[data-app-shell]"), rail: boxFor(".activity-rail"), - explorer: boxFor("#resource-explorer"), center: boxFor(".center-workspace"), right: boxFor(".right-sidebar"), commandBar: boxFor("#command-form"), @@ -6454,33 +6000,16 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { for (const [selector, label] of selectorLabels) { keyTargets.push(await inspectHitTarget(selector, label)); } - const resizeHandle = await inspectHitTarget("#explorer-resize", "左侧资源树宽度调整手柄"); const rightResizeHandle = await inspectHitTarget("#right-sidebar-resize", "右侧硬件状态栏宽度调整手柄"); - const drawerTargets = []; - for (const [selector, label] of drawerSelectorLabels) { - drawerTargets.push(await inspectFirstHit(selector, label)); - } const blockedKeyTargets = keyTargets.filter((target) => !target.ok); - const blockedDrawerTargets = drawerTargets.filter((target) => !target.ok); - const explorerCollapsed = shell?.classList.contains("explorer-collapsed") === true; - const explorerVisible = Boolean( - boxes.explorer && - boxes.explorer.display !== "none" && - boxes.explorer.visibility !== "hidden" && - boxes.explorer.width > 1 && - boxes.explorer.height > 1 - ); - const requireKeyTargets = !(isMobile && mode === "mobile-drawer"); - if (requireKeyTargets) { - for (const target of blockedKeyTargets) { - failures.push({ - failureType: target.missing ? "blocked/skip" : "covered-hit-target", - selector: target.selector, - viewport, - summary: `${target.label} is not center-click reachable; hit=${target.hit ?? "none"}`, - target - }); - } + for (const target of blockedKeyTargets) { + failures.push({ + failureType: target.missing ? "blocked/skip" : "covered-hit-target", + selector: target.selector, + viewport, + summary: `${target.label} is not center-click reachable; hit=${target.hit ?? "none"}`, + target + }); } const centerWidthDelta = compareTo?.boxes?.center?.width && boxes.center ? boxes.center.width - compareTo.boxes.center.width @@ -6490,7 +6019,6 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { : null; const overlapChecks = { railCenter: boxesOverlap(boxes.rail, boxes.center, -1), - explorerCenter: explorerVisible && isDesktop && boxesOverlap(boxes.explorer, boxes.center, -1), centerRight: isDesktop && boxesOverlap(boxes.center, boxes.right, -1), m3ActionsControlList: boxesOverlap(boxes.m3Actions, boxes.controlList, -1), topbarCommandBar: boxesOverlap(boxes.topbar, boxes.commandBar, -1) @@ -6667,119 +6195,24 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { controlTab?.click(); await new Promise((resolve) => requestAnimationFrame(resolve)); } - const toggle = { - text: toggleElement?.textContent?.replace(/\s+/gu, " ").trim() ?? "", - ariaLabel: toggleElement?.getAttribute("aria-label") ?? "", - title: toggleElement?.getAttribute("title") ?? "", - ariaExpanded: toggleElement?.getAttribute("aria-expanded") ?? "", - ariaPressed: toggleElement?.getAttribute("aria-pressed") ?? "" - }; - const collapsedWidthGain = isDesktop && mode === "desktop-collapsed" - ? centerWidthDelta !== null && centerWidthDelta >= 180 - : true; - const restoredWidth = isDesktop && mode === "desktop-restored" - ? centerWidthDelta !== null && Math.abs(centerWidthDelta) <= 4 - : true; - const drawerReachable = isMobile && mode === "mobile-drawer" - ? drawerTargets.length >= 3 && blockedDrawerTargets.length === 0 - : true; - for (const target of isMobile && mode === "mobile-drawer" ? blockedDrawerTargets : []) { - failures.push({ - failureType: target.missing ? "blocked/skip" : "covered-hit-target", - selector: target.selector, - viewport, - summary: `${target.label} drawer target is not reachable; hit=${target.hit ?? "none"}`, - target - }); - } - const collapsedExpected = mode === "desktop-collapsed" || mode === "mobile-collapsed" || mode === "mobile-recollapsed"; - const expandedExpected = mode === "desktop-expanded" || mode === "desktop-restored" || mode === "mobile-drawer"; - const collapsedStateMatches = collapsedExpected - ? explorerCollapsed === true && toggle.ariaExpanded === "false" && toggle.ariaPressed === "true" && toggle.text === "展开资源树" && toggle.ariaLabel === "展开左侧资源树" - : expandedExpected - ? explorerCollapsed === false && toggle.ariaExpanded === "true" && toggle.ariaPressed === "false" && toggle.text === "收起资源树" && toggle.ariaLabel === "收起左侧资源树" - : true; - const explorerStateMatches = collapsedExpected - ? isDesktop - ? boxes.explorer !== null && boxes.explorer.width <= 1 - : boxes.explorer?.display === "none" - : explorerVisible; - const defaultCollapsedMobile = mode === "mobile-collapsed" - ? explorerCollapsed === true - : true; + const removedSelectorStates = Object.fromEntries( + [ + ["#resource-explorer", "resourceExplorer"], + ["#explorer-resize", "explorerResize"], + ["#explorer-toggle", "explorerToggle"], + ["#resource-tree", "resourceTree"], + [".quick-actions", "quickActions"] + ].map(([selector, key]) => [key, document.querySelector(selector) === null]) + ); + const removedSelectorsAbsent = Object.values(removedSelectorStates).every(Boolean); const keyTargetsReachable = blockedKeyTargets.length === 0; - const keyTargetsRequirement = requireKeyTargets ? keyTargetsReachable : true; - if (!collapsedStateMatches) { - failures.push({ - failureType: "blocked/skip", - selector: "#explorer-toggle", - viewport, - summary: `Explorer toggle state/Chinese label mismatch in ${mode}: text=${toggle.text}, ariaLabel=${toggle.ariaLabel}, ariaExpanded=${toggle.ariaExpanded}, ariaPressed=${toggle.ariaPressed}.`, - toggle, - expected: collapsedExpected - ? { - explorerCollapsed: true, - text: "展开资源树", - ariaLabel: "展开左侧资源树", - ariaExpanded: "false", - ariaPressed: "true" - } - : { - explorerCollapsed: false, - text: "收起资源树", - ariaLabel: "收起左侧资源树", - ariaExpanded: "true", - ariaPressed: "false" - } - }); - } - if (!explorerStateMatches) { + if (!removedSelectorsAbsent) { failures.push({ failureType: "blocked/skip", selector: "#resource-explorer", viewport, - summary: `Explorer visibility did not match expected ${collapsedExpected ? "collapsed" : "expanded"} state in ${mode}.`, - explorerCollapsed, - explorerVisible, - explorerBox: boxes.explorer - }); - } - if (!collapsedWidthGain) { - failures.push({ - failureType: "overflow", - selector: ".center-workspace", - viewport, - summary: `Collapsed desktop layout did not release enough width to the center workspace; centerWidthDelta=${centerWidthDelta}.`, - centerWidthDelta, - centerBox: boxes.center - }); - } - if (!restoredWidth) { - failures.push({ - failureType: "overflow", - selector: ".center-workspace", - viewport, - summary: `Restored desktop layout did not return center width near the expanded baseline; centerWidthDelta=${centerWidthDelta}.`, - centerWidthDelta, - centerBox: boxes.center - }); - } - if (!drawerReachable) { - failures.push({ - failureType: "covered-hit-target", - selector: "#resource-explorer", - viewport, - summary: "Mobile resource drawer opened but its quick actions/resource rows were not reachable.", - blockedDrawerTargets - }); - } - if (!defaultCollapsedMobile) { - failures.push({ - failureType: "blocked/skip", - selector: "[data-app-shell]", - viewport, - summary: "Mobile default workbench did not start with the resource explorer collapsed.", - explorerCollapsed + summary: "Removed resource explorer selectors are still present in the default workbench DOM.", + removedSelectorStates }); } if (!rootScrollLocked) { @@ -6834,13 +6267,8 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { overflowOk && Object.values(noHorizontalOverflow).every(Boolean) && rightWidthTarget && - keyTargetsRequirement && - collapsedStateMatches && - explorerStateMatches && - collapsedWidthGain && - restoredWidth && - drawerReachable && - defaultCollapsedMobile && + keyTargetsReachable && + removedSelectorsAbsent && sidePanelOverflowUsable; const passWithWiring = pass && @@ -6853,16 +6281,13 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { mode, viewport, pass: passWithWiring, - explorerCollapsed, - explorerVisible, - toggle, + removedSelectorStates, + removedSelectorsAbsent, rootScrollLocked, rootAfterScrollAttempt, boxes, centerWidthDelta, rightWidthDelta, - collapsedWidthGain, - restoredWidth, rightPanelStacked, overlapChecks, noHorizontalOverflow, @@ -6871,12 +6296,8 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { rightWidthTarget, noIncoherentOverlap, keyTargetsReachable, - keyTargetsRequirement, blockedKeyTargets, - resizeHandle, rightResizeHandle, - drawerReachable, - blockedDrawerTargets, sidePanelScroll, sidePanelOverflowUsable, hardwareTabsCenterHit, @@ -6894,7 +6315,6 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { wiringReadableTwoColumn: wiringLongTableOk && wiringNoHorizontalScroll && wiringTextContained && wiringTextNonOverlapping, semanticOverlapChecks, overflowChecks, - defaultCollapsedMobile, issue287HardwareTabsReady, issue288SingleTableReady: false, skip, @@ -7157,7 +6577,7 @@ function firstFailure(check) { function allViewportCoverage(viewportResults, fieldName) { return Object.values(viewportResults).every((result) => - Boolean(result.expanded?.[fieldName] ?? result.collapsed?.[fieldName] ?? result.gate?.currentRoute?.[fieldName]) + Boolean(result.default?.[fieldName] ?? result.gate?.currentRoute?.[fieldName]) ); } @@ -7174,12 +6594,11 @@ function summarizeCoverageGap(viewportResults, issueKey) { Object.entries(viewportResults).map(([id, result]) => [ id, { - viewport: result.expanded?.viewport ?? result.collapsed?.viewport ?? result.gate?.currentRoute?.viewport ?? null, - covered: Boolean(result.expanded?.[fieldName] ?? result.collapsed?.[fieldName] ?? result.gate?.currentRoute?.[fieldName]), + viewport: result.default?.viewport ?? result.gate?.currentRoute?.viewport ?? null, + covered: Boolean(result.default?.[fieldName] ?? result.gate?.currentRoute?.[fieldName]), currentVisibleContainersChecked: true, skip: [ - ...(result.expanded?.skip ?? []), - ...(result.collapsed?.skip ?? []), + ...(result.default?.skip ?? []), ...(result.gate?.currentRoute?.skip ?? []) ].filter((item) => issueKey === "issue288" ? item.summary?.includes("#288") @@ -7300,7 +6719,7 @@ async function inspectHelpMarkdownRoute(page) { codeCount, hasRawMarkdownHeading: Boolean(helpContent?.textContent?.includes("# 云工作台内部使用说明")), termsPresent: - ["左侧资源与功能导航", "Agent 对话与工作清单", "same-origin", "/v1", ":16666"].every((term) => text.includes(term)) && + ["活动栏与默认工作区", "Agent 对话与工作清单", "same-origin", "/v1", ":16666"].every((term) => text.includes(term)) && codeCount >= 8, helpPanelScrolls: Boolean(helpScroll && helpScroll.scrollHeight > helpScroll.clientHeight && helpScroll.after > helpScroll.before), rootStillLocked, diff --git a/web/hwlab-cloud-web/app.mjs b/web/hwlab-cloud-web/app.mjs index 0975a769..442d28e2 100644 --- a/web/hwlab-cloud-web/app.mjs +++ b/web/hwlab-cloud-web/app.mjs @@ -79,9 +79,6 @@ const CODEX_STDIO_BLOCKER_MARKERS = Object.freeze([ ]); const UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN = /\b(?:echo|mock|fixture|stub|sample)\b/iu; const LAYOUT_STORAGE_KEY = "hwlab.workbench.layout.v1"; -const EXPLORER_DEFAULT_WIDTH = 292; -const EXPLORER_MIN_WIDTH = 220; -const EXPLORER_MAX_WIDTH = 420; const RIGHT_SIDEBAR_DEFAULT_WIDTH = 620; const RIGHT_SIDEBAR_MIN_WIDTH = 560; const RIGHT_SIDEBAR_MAX_WIDTH = 760; @@ -91,14 +88,7 @@ let rpcSequence = 0; const el = { shell: query("[data-app-shell]"), - explorerToggle: byId("explorer-toggle"), - explorerResize: byId("explorer-resize"), rightSidebarResize: byId("right-sidebar-resize"), - explorerStatus: byId("explorer-status"), - treeCount: byId("tree-count"), - resourceTree: byId("resource-tree"), - capabilityCount: byId("capability-count"), - explorerCapabilities: byId("explorer-capabilities"), routePath: byId("route-path"), liveStatus: byId("live-status"), liveDetail: byId("live-detail"), @@ -159,18 +149,13 @@ const state = { pending: false }, layout: { - explorerWidth: EXPLORER_DEFAULT_WIDTH, rightSidebarWidth: RIGHT_SIDEBAR_DEFAULT_WIDTH, - drag: null, rightDrag: null } }; initRoutes(); initLayoutSizing(); -initExplorerToggle(); -syncMobileExplorer(); -initExplorerResize(); initRightSidebarResize(); initSideTabs(); initHardwareTabs(); @@ -219,9 +204,7 @@ function initRoutes() { function initLayoutSizing() { const persisted = readPersistedLayout(); - const explorerWidth = clampExplorerWidth(persisted?.explorerWidth ?? EXPLORER_DEFAULT_WIDTH); const rightSidebarWidth = clampRightSidebarWidth(persisted?.rightSidebarWidth ?? RIGHT_SIDEBAR_DEFAULT_WIDTH); - setExplorerWidth(explorerWidth, { persist: false }); setRightSidebarWidth(rightSidebarWidth, { persist: false }); } @@ -232,7 +215,6 @@ function readPersistedLayout() { const payload = JSON.parse(raw); if (payload?.version !== 1) return null; return { - explorerWidth: Number(payload.explorerWidth), rightSidebarWidth: Number(payload.rightSidebarWidth) }; } catch { @@ -246,7 +228,6 @@ function persistLayout() { LAYOUT_STORAGE_KEY, JSON.stringify({ version: 1, - explorerWidth: state.layout.explorerWidth, rightSidebarWidth: state.layout.rightSidebarWidth }) ); @@ -255,16 +236,6 @@ function persistLayout() { } } -function setExplorerWidth(width, options = {}) { - const clamped = clampExplorerWidth(width); - state.layout.explorerWidth = clamped; - el.shell.style.setProperty("--explorer-width", `${clamped}px`); - syncExplorerResizeA11y(); - syncRightSidebarResizeA11y(); - if (options.persist !== false) persistLayout(); - return clamped; -} - function setRightSidebarWidth(width, options = {}) { const clamped = clampRightSidebarWidth(width); state.layout.rightSidebarWidth = clamped; @@ -274,13 +245,6 @@ function setRightSidebarWidth(width, options = {}) { return clamped; } -function clampExplorerWidth(value) { - const numeric = Number(value); - const width = Number.isFinite(numeric) ? numeric : EXPLORER_DEFAULT_WIDTH; - const bounds = explorerResizeBounds(); - return Math.min(Math.max(Math.round(width), bounds.min), bounds.max); -} - function clampRightSidebarWidth(value) { const numeric = Number(value); const width = Number.isFinite(numeric) ? numeric : RIGHT_SIDEBAR_DEFAULT_WIDTH; @@ -288,24 +252,6 @@ function clampRightSidebarWidth(value) { return Math.min(Math.max(Math.round(width), bounds.min), bounds.max); } -function explorerResizeBounds() { - const styles = getComputedStyle(el.shell); - const cssMin = readCssPixel(styles.getPropertyValue("--explorer-min-width"), EXPLORER_MIN_WIDTH); - const cssMax = readCssPixel(styles.getPropertyValue("--explorer-max-width"), EXPLORER_MAX_WIDTH); - if (window.matchMedia("(max-width: 860px)").matches) { - return { min: EXPLORER_MIN_WIDTH, max: EXPLORER_MAX_WIDTH }; - } - if (window.matchMedia("(max-width: 1240px)").matches) { - const railWidth = readCssPixel(styles.getPropertyValue("--rail-width"), 86); - const dynamicMax = window.innerWidth - railWidth - 420 - 4; - return boundedResizeRange(cssMin, cssMax, dynamicMax); - } - const railWidth = readCssPixel(styles.getPropertyValue("--rail-width"), 92); - const rightWidth = Math.min(Math.max(window.innerWidth * 0.45, 560), 740); - const dynamicMax = window.innerWidth - railWidth - 360 - rightWidth - 4; - return boundedResizeRange(cssMin, cssMax, dynamicMax); -} - function boundedResizeRange(min, max, dynamicMax) { const safeMin = Math.max(0, Math.round(min)); const safeMax = Math.max(safeMin, Math.round(Math.min(max, Number.isFinite(dynamicMax) ? dynamicMax : max))); @@ -320,9 +266,7 @@ function rightSidebarResizeBounds() { return { min: 0, max: Math.max(0, window.innerWidth) }; } const railWidth = readCssPixel(styles.getPropertyValue("--rail-width"), 92); - const explorerWidth = el.shell.classList.contains("explorer-collapsed") ? 0 : state.layout.explorerWidth; - const centerMinWidth = el.shell.classList.contains("explorer-collapsed") ? 420 : 360; - const dynamicMax = window.innerWidth - railWidth - explorerWidth - centerMinWidth - 4; + const dynamicMax = window.innerWidth - railWidth - 420 - 4; return boundedResizeRange(cssMin, cssMax, dynamicMax); } @@ -331,14 +275,6 @@ function readCssPixel(value, fallback) { return Number.isFinite(parsed) ? parsed : fallback; } -function syncExplorerResizeA11y() { - const bounds = explorerResizeBounds(); - el.explorerResize.setAttribute("aria-valuemin", String(bounds.min)); - el.explorerResize.setAttribute("aria-valuemax", String(bounds.max)); - el.explorerResize.setAttribute("aria-valuenow", String(state.layout.explorerWidth)); - el.explorerResize.setAttribute("aria-valuetext", `左侧资源树宽度 ${state.layout.explorerWidth} 像素`); -} - function syncRightSidebarResizeA11y() { const bounds = rightSidebarResizeBounds(); const disabled = !canResizeRightSidebar(); @@ -374,57 +310,6 @@ function showView(route) { } } -function initExplorerToggle() { - el.explorerToggle.addEventListener("click", () => { - const collapsed = !el.shell.classList.contains("explorer-collapsed"); - setExplorerCollapsed(collapsed); - }); -} - -function initExplorerResize() { - window.addEventListener("resize", () => { - if (canResizeExplorer()) setExplorerWidth(state.layout.explorerWidth, { persist: false }); - syncExplorerResizeA11y(); - }); - el.explorerResize.addEventListener("pointerdown", (event) => { - if (!canResizeExplorer()) return; - event.preventDefault(); - el.explorerResize.setPointerCapture?.(event.pointerId); - state.layout.drag = { - pointerId: event.pointerId, - startX: event.clientX, - startWidth: state.layout.explorerWidth - }; - el.shell.classList.add("is-resizing"); - }); - el.explorerResize.addEventListener("pointermove", (event) => { - const drag = state.layout.drag; - if (!drag || drag.pointerId !== event.pointerId) return; - setExplorerWidth(drag.startWidth + event.clientX - drag.startX, { persist: false }); - }); - el.explorerResize.addEventListener("pointerup", finishExplorerResize); - el.explorerResize.addEventListener("pointercancel", finishExplorerResize); - el.explorerResize.addEventListener("keydown", (event) => { - if (!canResizeExplorer()) return; - const keySteps = { - ArrowLeft: -16, - ArrowRight: 16, - PageDown: -48, - PageUp: 48 - }; - if (Object.hasOwn(keySteps, event.key)) { - event.preventDefault(); - setExplorerWidth(state.layout.explorerWidth + keySteps[event.key]); - } else if (event.key === "Home") { - event.preventDefault(); - setExplorerWidth(explorerResizeBounds().min); - } else if (event.key === "End") { - event.preventDefault(); - setExplorerWidth(explorerResizeBounds().max); - } - }); -} - function initRightSidebarResize() { window.addEventListener("resize", () => { if (canResizeRightSidebar()) setRightSidebarWidth(state.layout.rightSidebarWidth, { persist: false }); @@ -469,14 +354,6 @@ function initRightSidebarResize() { }); } -function finishExplorerResize(event) { - const drag = state.layout.drag; - if (!drag || drag.pointerId !== event.pointerId) return; - state.layout.drag = null; - el.shell.classList.remove("is-resizing"); - persistLayout(); -} - function finishRightSidebarResize(event) { const drag = state.layout.rightDrag; if (!drag || drag.pointerId !== event.pointerId) return; @@ -485,45 +362,10 @@ function finishRightSidebarResize(event) { persistLayout(); } -function canResizeExplorer() { - return !window.matchMedia("(max-width: 860px)").matches && !el.shell.classList.contains("explorer-collapsed"); -} - function canResizeRightSidebar() { return !window.matchMedia("(max-width: 1240px)").matches; } -function setExplorerCollapsed(collapsed) { - if (!collapsed) { - setExplorerWidth(state.layout.explorerWidth, { persist: false }); - } - el.shell.classList.toggle("explorer-collapsed", collapsed); - el.explorerToggle.setAttribute("aria-pressed", collapsed ? "true" : "false"); - el.explorerToggle.setAttribute("aria-expanded", collapsed ? "false" : "true"); - el.explorerToggle.setAttribute("aria-label", collapsed ? "展开左侧资源树" : "收起左侧资源树"); - el.explorerToggle.title = collapsed ? "展开左侧资源树,恢复导航和硬件资源" : "收起左侧资源树,给工作区更多宽度"; - el.explorerToggle.textContent = collapsed ? "展开资源树" : "收起资源树"; - const resizeDisabled = collapsed || window.matchMedia("(max-width: 860px)").matches; - el.explorerResize.tabIndex = resizeDisabled ? -1 : 0; - el.explorerResize.setAttribute("aria-disabled", resizeDisabled ? "true" : "false"); - el.explorerResize.setAttribute("aria-hidden", resizeDisabled ? "true" : "false"); - setRightSidebarWidth(state.layout.rightSidebarWidth, { persist: false }); -} - -function syncMobileExplorer() { - const media = window.matchMedia("(max-width: 860px)"); - const apply = () => { - if (media.matches) { - setExplorerCollapsed(true); - } else { - setExplorerCollapsed(false); - setExplorerWidth(state.layout.explorerWidth, { persist: false }); - } - }; - apply(); - media.addEventListener("change", apply); -} - async function loadHelpSurface() { el.helpStatus.textContent = "加载中"; el.helpStatus.className = "state-tag tone-dry-run"; @@ -558,22 +400,14 @@ function initSideTabs() { for (const jump of document.querySelectorAll("[data-side-tab-jump]")) { jump.addEventListener("click", () => { selectSideTab(jump.dataset.sideTabJump); - collapseExplorerAfterMobileAction(); }); } } function initQuickActions() { - for (const button of document.querySelectorAll("[data-focus-command]")) { - button.addEventListener("click", () => { - el.commandInput.focus(); - collapseExplorerAfterMobileAction(); - }); - } for (const button of document.querySelectorAll("[data-agent-quick-prompt]")) { button.addEventListener("click", () => { fillAgentQuickPrompt(button); - collapseExplorerAfterMobileAction(); }); } } @@ -586,12 +420,6 @@ function fillAgentQuickPrompt(button) { el.commandInput.setSelectionRange(prompt.length, prompt.length); } -function collapseExplorerAfterMobileAction() { - if (window.matchMedia("(max-width: 860px)").matches) { - setExplorerCollapsed(true); - } -} - function selectSideTab(tabId) { for (const tab of document.querySelectorAll("[data-side-tab]")) { const active = tab.dataset.sideTab === tabId; @@ -788,12 +616,8 @@ function initGateControls() { } function renderStaticWorkbench() { - el.explorerStatus.textContent = "只读工作区"; - el.explorerStatus.className = "status-dot tone-source"; el.routePath.textContent = "当前浏览器会话只保存任务草稿,硬件动作需通过受控后端流程。"; - renderResourceTree(); - renderCapabilityList(); renderConversation(); renderTaskList(); renderHardwareStatus(null); @@ -1125,86 +949,6 @@ function workbenchApiSurfaceStatus(live, coreProbes = [live.healthLive, live.res }; } -function renderResourceTree() { - const items = [ - { - label: gateSummary.topology.projectId, - meta: "当前项目", - tone: "source", - children: [ - ...gateSummary.topology.gateways.map((gateway) => ({ - label: gateway.gatewaySessionId, - meta: `${gateway.serviceId} / ${statusLabel(gateway.status)}`, - tone: gateway.status - })), - ...gateSummary.topology.boxResources.map((resource) => ({ - label: resource.resourceId, - meta: `${resourceTypeLabel(resource.resourceType)} / ${statusLabel(resource.state)}`, - tone: resource.state - })), - { - label: gateSummary.topology.patchPanel.patchPanelStatusId, - meta: `${gateSummary.topology.patchPanel.serviceId} / ${statusLabel(gateSummary.topology.patchPanel.state)}`, - tone: gateSummary.topology.patchPanel.state - } - ] - }, - { - label: gateSummary.agent.agentSessionId, - meta: `${gateSummary.agent.agentServiceId} / Agent 会话`, - tone: gateSummary.agent.agentStatus, - children: [ - { - label: gateSummary.agent.workerSessionId, - meta: `${gateSummary.agent.workerServiceId} / ${statusLabel(gateSummary.agent.workerStatus)}`, - tone: gateSummary.agent.workerStatus - } - ] - } - ]; - - el.treeCount.textContent = String(items.reduce((count, item) => count + 1 + (item.children?.length ?? 0), 0)); - replaceChildren(el.resourceTree, ...items.map(treeGroup)); -} - -function treeGroup(item) { - const details = document.createElement("details"); - details.open = true; - const summary = document.createElement("summary"); - summary.append(statusLight(item.tone), textSpan(item.label, "tree-label"), textSpan(item.meta, "tree-meta")); - details.append(summary); - for (const child of item.children ?? []) { - const row = document.createElement("button"); - row.type = "button"; - row.className = "tree-row"; - row.append(statusLight(child.tone), textSpan(child.label, "tree-label"), textSpan(child.meta, "tree-meta")); - details.append(row); - } - return details; -} - -function renderCapabilityList() { - const capabilities = [ - { - title: "任务草稿", - detail: "在底部输入区记录要交给 Agent 的任务,不会直接发送硬件请求。", - tone: "source" - }, - { - title: "资源查看", - detail: "查看 Gateway-SIMU、BOX-SIMU 与 hwlab-patch-panel 的只读资源状态。", - tone: "source" - }, - { - title: "接线核对", - detail: "在右侧两列接线表核对互连设备、DO/DI 对应关系与证据记录。", - tone: "source" - } - ]; - el.capabilityCount.textContent = String(capabilities.length); - replaceChildren(el.explorerCapabilities, ...capabilities.map(infoCard)); -} - function renderConversation() { const introMessages = [ { @@ -2785,12 +2529,6 @@ function infoCard(item) { return article; } -function statusLight(tone) { - const span = document.createElement("span"); - span.className = `tree-light tone-bg-${toneClass(tone)}`; - return span; -} - function badge(text, tone = text) { const span = document.createElement("span"); span.className = `badge tone-${toneClass(tone)}`; @@ -3252,15 +2990,6 @@ function roleLabel(role) { ); } -function resourceTypeLabel(type) { - return ( - { - board: "板卡", - relay: "继电器" - }[String(type ?? "").toLowerCase()] ?? statusLabel(type) - ); -} - function recordKindLabel(kind) { return ( { diff --git a/web/hwlab-cloud-web/help.md b/web/hwlab-cloud-web/help.md index c0d8d8b8..2614256a 100644 --- a/web/hwlab-cloud-web/help.md +++ b/web/hwlab-cloud-web/help.md @@ -2,12 +2,12 @@ 本页面说明当前 Cloud Workbench 的前端区域与状态含义。默认 `/` 仍进入工作台;本说明只通过工作台内部的 `#help` 入口打开,不作为默认页。 -## 左侧资源与功能导航 +## 活动栏与默认工作区 -- 活动栏使用完整中文标签:`工作台` 回到默认工作台,`内部复核` 打开只读复核表,`使用说明` 打开本说明,`资源树` 展开或收起资源树。 +- 活动栏使用完整中文标签:`工作台` 回到默认工作台,`内部复核` 打开只读复核表,`使用说明` 打开本说明。 - 左侧不再放独立的可信记录跳转;可信记录保留在右侧子面板。 -- 资源树只展示当前项目、Gateway-SIMU、BOX-SIMU、`hwlab-patch-panel`、Agent manager / worker 等用户可理解资源,不把 M0-M5 Gate 状态放在默认首屏。 -- 常用能力区提供任务草稿、资源查看与接线核对入口。`LOCAL` 只表示浏览器本地草稿,不等同于 DEV-LIVE。 +- 默认首屏不再放资源树、能力卡片或左侧抽屉,空间直接分配给 Code Agent 对话、执行证据、硬件状态、接线和控制。 +- `LOCAL` 只表示浏览器本地草稿,不等同于 DEV-LIVE。 ## 中间 Agent 对话与工作清单 diff --git a/web/hwlab-cloud-web/index.html b/web/hwlab-cloud-web/index.html index 883f88da..344d1e22 100644 --- a/web/hwlab-cloud-web/index.html +++ b/web/hwlab-cloud-web/index.html @@ -14,49 +14,6 @@ - - - -
diff --git a/web/hwlab-cloud-web/scripts/check.mjs b/web/hwlab-cloud-web/scripts/check.mjs index 25c96e8f..cf525dd8 100644 --- a/web/hwlab-cloud-web/scripts/check.mjs +++ b/web/hwlab-cloud-web/scripts/check.mjs @@ -245,17 +245,9 @@ assert.equal( "#288 single-table gate is future coverage; current /gate route remains covered by layout smoke" ); for (const layoutCheckId of [ - "layout-desktop-expanded", - "layout-desktop-collapsed", - "layout-desktop-restored", - "layout-narrow-desktop-expanded", - "layout-narrow-desktop-collapsed", - "layout-narrow-desktop-restored", - "layout-mobile-collapsed", - "layout-mobile-drawer", - "layout-explorer-resize-desktop", - "layout-explorer-resize-narrow-desktop", - "layout-explorer-resize-mobile", + "layout-desktop-default", + "layout-narrow-desktop-default", + "layout-mobile-default", "layout-right-sidebar-resize-desktop", "layout-right-sidebar-resize-narrow-desktop", "layout-right-sidebar-resize-mobile", @@ -387,10 +379,6 @@ assert.match(html, /href="\/styles\.css"/); assert.match(html, /src="\/app\.mjs"/); for (const workbenchElement of [ "activity-rail", - "explorer", - "explorer-resize", - "resource-explorer", - "resource-tree", "conversation-list", "agent-chat-status", "code-agent-summary", @@ -410,29 +398,41 @@ for (const workbenchElement of [ ]) { assert.match(html, new RegExp(workbenchElement)); } -assert.match(html, /id="explorer-toggle"/); -assert.match(html, /aria-controls="resource-explorer"/); -assert.match(html, /aria-expanded="true"/); -assert.match(html, /aria-pressed="false"/); -assert.match(html, /aria-label="收起左侧资源树"/); -assert.match(html, />收起资源树<\/button>/); -assert.match(html, /id="explorer-resize"/); -assert.match(html, /role="separator"/); -assert.match(html, /aria-label="拖拽调整左侧资源树宽度"/); -assert.match(html, /aria-valuemin="220"/); -assert.match(html, /aria-valuemax="420"/); -assert.match(html, /aria-valuenow="292"/); +for (const removedSelectorTerm of [ + "id=\"resource-explorer\"", + "id=\"explorer-toggle\"", + "id=\"explorer-resize\"", + "id=\"resource-tree\"", + "explorer-capabilities", + "capability-count", + "tree-count", + "quick-actions", + "data-focus-command" +]) { + assert.doesNotMatch(html, new RegExp(escapeRegExp(removedSelectorTerm)), `removed resource explorer selector returned: ${removedSelectorTerm}`); +} +for (const removedUiText of [ + "资源浏览与硬件资源树", + "只读工作区", + "常用能力", + "收起资源树", + "展开资源树", + "拖拽调整左侧资源树宽度", + "资源查看", + "查看接线", + "查看状态", + "编写任务" +]) { + assert.doesNotMatch(`${html}\n${app}\n${styles}`, new RegExp(escapeRegExp(removedUiText)), `removed resource explorer copy returned: ${removedUiText}`); +} assert.match(html, /id="hardware-sidebar"/); assert.match(html, /id="right-sidebar-resize"/); assert.match(html, /aria-label="拖拽调整右侧硬件状态栏宽度"/); assert.match(html, /aria-valuemin="560"/); assert.match(html, /aria-valuemax="760"/); assert.match(html, /aria-valuenow="620"/); -assert.match(app, /setAttribute\("aria-label", collapsed \? "展开左侧资源树" : "收起左侧资源树"\)/); -assert.match(app, /textContent = collapsed \? "展开资源树" : "收起资源树"/); +assert.doesNotMatch(app, /setExplorerCollapsed|initExplorerResize|clampExplorerWidth|syncMobileExplorer|collapseExplorerAfterMobileAction/); assert.match(app, /LAYOUT_STORAGE_KEY\s*=\s*"hwlab\.workbench\.layout\.v1"/); -assert.match(app, /function initExplorerResize/); -assert.match(app, /function clampExplorerWidth/); assert.match(app, /function initRightSidebarResize/); assert.match(app, /function clampRightSidebarWidth/); assert.match(app, /function rightSidebarResizeBounds/); @@ -443,20 +443,14 @@ assert.match(app, /ArrowLeft/); assert.match(app, /ArrowRight/); assert.match(app, /event\.key === "Home"/); assert.match(app, /event\.key === "End"/); -assert.match(styles, /--explorer-width:\s*292px/); -assert.match(styles, /--explorer-min-width:\s*220px/); -assert.match(styles, /--explorer-max-width:\s*420px/); +assert.doesNotMatch(styles, /--explorer-width|--explorer-min-width|--explorer-max-width|\.explorer|\.explorer-resize|\.quick-actions|\.resource-tree|\.tree-panel|\.tree-row|\.tree-label/); assert.match(styles, /--right-sidebar-width:\s*620px/); assert.match(styles, /--right-sidebar-min-width:\s*560px/); assert.match(styles, /--right-sidebar-max-width:\s*760px/); assert.match(styles, /--right-width:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/); assert.match(styles, /--right-width-expanded:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/); -assert.match(styles, /\.explorer-resize\s*{[^}]*cursor:\s*col-resize;/s); assert.match(styles, /\.right-sidebar-resize\s*{[^}]*cursor:\s*col-resize;/s); -assert.match(styles, /\.explorer-collapsed \.explorer-resize\s*{[^}]*display:\s*none;/s); -assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.explorer-resize\s*{[\s\S]*?display:\s*none;/); assert.match(styles, /@media \(max-width: 1240px\)[\s\S]*?\.right-sidebar-resize\s*{[\s\S]*?display:\s*none;/); -assert.match(styles, /\.explorer-collapsed\s*\{[^}]*--explorer-width:\s*0px;[^}]*grid-template-columns:\s*var\(--rail-width\)\s+0\s+minmax\(420px,\s*1fr\)\s+var\(--right-width\);/s); for (const viewId of ["workspace", "gate"]) { assert.match(html, new RegExp(`data-view="${viewId}"`)); } @@ -481,7 +475,7 @@ assert.match(styles, /body\s*>\s*\[data-app-shell\]\s*{[^}]*height:\s*100%;[^}]* assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100dvh;[^}]*max-height:\s*100dvh;[^}]*overflow:\s*hidden;[^}]*overscroll-behavior:\s*contain;/s); assert.match(helpMarkdown, /^# 云工作台内部使用说明/m); for (const helpTerm of [ - "左侧资源与功能导航", + "活动栏与默认工作区", "Agent 对话与工作清单", "完整中文标签", "内部复核", @@ -512,7 +506,7 @@ const defaultFirstViewportText = visibleTextFromHtml(defaultFirstViewportHtml(ht for (const backendTerm of ["Gate", "诊断", "验收", "BLOCKED", "SOURCE", "DRY-RUN", "DEV-LIVE", "M0-M5", "M3 基本", "trace/evidence", "执行轨迹", "OPENAI_API_KEY", "Secret", "secretRef", "hwlab-code-agent-provider", "m3-route-required", "waiting-for-dev-live"]) { assert.equal(textHasForbiddenTerm(defaultFirstViewportText, backendTerm), false, `default workbench must not expose backend term ${backendTerm}`); } -for (const fullNavLabel of ["工作台", "内部复核", "使用说明", "收起资源树"]) { +for (const fullNavLabel of ["工作台", "内部复核", "使用说明"]) { assert.match(html, new RegExp(`>${escapeRegExp(fullNavLabel)}<`), `missing full nav label ${fullNavLabel}`); } for (const oneCharNavLabel of [">台<", ">证<", ">诊<", ">帮<"]) { @@ -625,10 +619,6 @@ assert.doesNotMatch(app, /API 错误 \/ 只读模式/); assert.doesNotMatch(app, /可信记录受阻 \/ 只读模式/); assert.doesNotMatch(app, /API 降级 \/ 只读模式/); assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /runtime_durable_adapter_query_blocked|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u); -assert.match(html, /quick-actions/); -assert.match(html, /编写任务/); -assert.match(html, /查看接线/); -assert.match(html, /查看状态/); assert.match(html, /placeholder="输入要发送给 Code Agent 的消息;不会直接触发硬件变更。"/); assert.match(html, />发送<\/button>/); assert.doesNotMatch(html, />添加草稿<\/button>/); @@ -828,15 +818,8 @@ assert.match(styles, /html,\s*\nbody\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hid assert.match(styles, /body\s*>\s*\[data-app-shell\]\s*{[^}]*min-height:\s*0;/s); assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100(?:d)?vh;[^}]*overflow:\s*hidden;/s); assert.match(styles, /\.view\s*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); -assert.match(styles, /\.(?:resource-tree|compact-list|conversation-list|task-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); -assert.match(app, /function syncMobileExplorer/); -assert.match(app, /window\.matchMedia\("\(max-width: 860px\)"\)/); -assert.match(app, /setExplorerCollapsed\(true\)/); -assert.match(app, /function collapseExplorerAfterMobileAction/); -assert.match(functionBody(app, "setExplorerCollapsed"), /aria-expanded/); -assert.match(styles, /\.explorer-collapsed \.explorer\s*{[^}]*pointer-events:\s*none;/s); +assert.match(styles, /\.(?:compact-list|conversation-list|task-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.activity-rail\s*{[\s\S]*?grid-row:\s*1 \/ 3;/); -assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.explorer\s*{[\s\S]*?grid-row:\s*1 \/ 3;[\s\S]*?z-index:\s*4;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.center-workspace\s*{[\s\S]*?grid-row:\s*1;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.right-sidebar\s*{[\s\S]*?grid-row:\s*2;/); assert.match(styles, /@media \(max-width: 520px\)[\s\S]*?\.command-bar\s*{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto auto;/); diff --git a/web/hwlab-cloud-web/styles.css b/web/hwlab-cloud-web/styles.css index d42eb7bd..e7c8f88a 100644 --- a/web/hwlab-cloud-web/styles.css +++ b/web/hwlab-cloud-web/styles.css @@ -18,9 +18,6 @@ --bad: #e76e5e; --info: #7ea6c9; --rail-width: 92px; - --explorer-width: 292px; - --explorer-min-width: 220px; - --explorer-max-width: 420px; --right-sidebar-width: 620px; --right-sidebar-min-width: 560px; --right-sidebar-max-width: 760px; @@ -83,7 +80,7 @@ ul { max-height: 100dvh; min-height: 0; display: grid; - grid-template-columns: var(--rail-width) var(--explorer-width) minmax(360px, 1fr) var(--right-width-expanded); + grid-template-columns: var(--rail-width) minmax(420px, 1fr) var(--right-width-expanded); grid-template-rows: 1fr; overflow: hidden; overscroll-behavior: contain; @@ -91,7 +88,6 @@ ul { } .activity-rail, -.explorer, .right-sidebar, .center-workspace { min-width: 0; @@ -100,7 +96,7 @@ ul { .activity-rail { display: grid; - grid-template-rows: repeat(3, minmax(42px, auto)) 1fr minmax(42px, auto); + grid-template-rows: repeat(3, minmax(42px, auto)) minmax(0, 1fr); gap: 6px; padding: 8px; background: var(--rail); @@ -129,10 +125,6 @@ ul { word-break: keep-all; } -#explorer-toggle { - min-height: 44px; -} - .rail-button:hover, .rail-button:focus-visible, .rail-button.active { @@ -146,22 +138,6 @@ ul { box-shadow: inset 3px 0 0 var(--accent); } -.explorer { - position: relative; - display: grid; - grid-template-rows: auto auto minmax(0, 1fr) auto; - gap: 8px; - padding: 10px 14px 10px 10px; - background: rgba(22, 25, 22, 0.98); - border-right: 1px solid var(--line); - overflow: hidden; -} - -.explorer-collapsed { - --explorer-width: 0px; - grid-template-columns: var(--rail-width) 0 minmax(420px, 1fr) var(--right-width); -} - .workbench-shell.is-resizing { cursor: col-resize; user-select: none; @@ -181,21 +157,6 @@ ul { opacity: 0.56; } -.explorer-resize { - top: 0; - right: 0; - width: 12px; - height: 100%; - cursor: col-resize; -} - -.explorer-resize::before { - top: 10px; - bottom: 10px; - left: 5px; - width: 2px; -} - .right-sidebar-resize { top: 0; left: 0; @@ -223,25 +184,6 @@ ul { outline-offset: -2px; } -.explorer-collapsed .explorer { - padding: 0; - border-right: 0; - overflow: hidden; -} - -.explorer-collapsed .explorer > * { - display: none; -} - -.explorer-collapsed .explorer { - pointer-events: none; -} - -.explorer-collapsed .explorer-resize { - display: none; -} - -.explorer-head, .topbar, .hardware-status, .side-workspace, @@ -250,15 +192,6 @@ ul { border: 1px solid var(--line); } -.explorer-head { - min-height: 78px; - display: flex; - justify-content: space-between; - gap: 10px; - align-items: start; - padding: 12px; -} - .eyebrow { color: var(--accent-2); font-size: 10px; @@ -305,42 +238,6 @@ h3 { overflow-wrap: anywhere; } -.quick-actions { - display: grid; - grid-template-columns: 1fr; - gap: 6px; -} - -.quick-actions button { - width: 100%; - min-height: 32px; - padding: 6px 8px; - border: 1px solid var(--line); - background: var(--surface-2); - color: var(--text); - font-weight: 760; - text-align: left; - cursor: pointer; -} - -.quick-actions button:hover, -.quick-actions button:focus-visible { - border-color: var(--line-strong); - background: var(--surface-3); - outline: 0; -} - -.tree-panel { - min-height: 0; - display: grid; - grid-template-rows: auto minmax(0, 1fr); - gap: 8px; - padding: 10px; - background: var(--surface); - border: 1px solid var(--line); - overflow: hidden; -} - .blockers-panel { max-height: 230px; } @@ -362,7 +259,6 @@ h3 { color: var(--muted); } -.resource-tree, .compact-list, .conversation-list, .task-list, @@ -371,70 +267,6 @@ h3 { overflow: auto; } -.resource-tree { - display: grid; - align-content: start; - gap: 6px; -} - -.resource-tree details { - border: 1px solid var(--line); - background: var(--surface-2); -} - -.resource-tree summary, -.tree-row { - width: 100%; - min-height: 31px; - display: grid; - grid-template-columns: 8px minmax(0, 1fr); - gap: 7px; - align-items: center; - padding: 7px 8px; - border: 0; - background: transparent; - text-align: left; -} - -.resource-tree summary { - cursor: pointer; -} - -.tree-row { - padding-left: 22px; - border-top: 1px solid var(--line); - cursor: pointer; -} - -.tree-row:hover { - background: rgba(212, 173, 67, 0.06); -} - -.tree-label, -.tree-meta { - display: block; - min-width: 0; - overflow-wrap: anywhere; -} - -.tree-label { - color: var(--text); - font-family: var(--mono); - font-size: 11px; -} - -.tree-meta { - grid-column: 2; - color: var(--muted); - font-size: 11px; -} - -.tree-light { - width: 8px; - height: 8px; - background: var(--dim); -} - .center-workspace { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; @@ -1741,20 +1573,12 @@ tbody tr:last-child td { @media (max-width: 1240px) { .workbench-shell { --rail-width: 86px; - --explorer-width: 260px; - --explorer-min-width: 210px; - --explorer-max-width: 320px; - grid-template-columns: var(--rail-width) var(--explorer-width) minmax(420px, 1fr); + grid-template-columns: var(--rail-width) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) clamp(220px, 36dvh, 360px); } - .explorer-collapsed { - --explorer-width: 0px; - grid-template-columns: var(--rail-width) 0 minmax(0, 1fr); - } - .right-sidebar { - grid-column: 2 / 4; + grid-column: 2; grid-row: 2; min-height: 0; padding-left: 10px; @@ -1768,11 +1592,8 @@ tbody tr:last-child td { } @media (max-width: 860px) { - .workbench-shell, - .explorer-collapsed { + .workbench-shell { --rail-width: 68px; - --explorer-min-width: 0px; - --explorer-max-width: 0px; grid-template-columns: var(--rail-width) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) clamp(214px, 30dvh, 280px); } @@ -1780,7 +1601,7 @@ tbody tr:last-child td { .activity-rail { grid-column: 1; grid-row: 1 / 3; - grid-template-rows: repeat(3, minmax(42px, auto)) minmax(12px, 1fr) minmax(42px, auto); + grid-template-rows: repeat(3, minmax(42px, auto)) minmax(12px, 1fr); overflow: hidden; } @@ -1789,20 +1610,6 @@ tbody tr:last-child td { line-height: 1.15; } - .explorer { - grid-column: 2; - grid-row: 1 / 3; - z-index: 4; - max-height: 100%; - padding: 10px; - overflow: auto; - box-shadow: -1px 0 0 var(--line), 12px 0 24px rgba(0, 0, 0, 0.24); - } - - .explorer-resize { - display: none; - } - .right-sidebar-resize { display: none; } @@ -1824,10 +1631,6 @@ tbody tr:last-child td { grid-template-rows: auto minmax(0, auto) minmax(0, 1fr); } - .explorer-collapsed .explorer { - display: none; - } - .topbar { min-height: 0; gap: 8px; @@ -1889,8 +1692,7 @@ tbody tr:last-child td { .hardware-list, .m3-control-grid, - .m3-control-actions, - .quick-actions { + .m3-control-actions { grid-template-columns: 1fr; } @@ -1939,8 +1741,7 @@ tbody tr:last-child td { } @media (max-width: 520px) { - .workbench-shell, - .explorer-collapsed { + .workbench-shell { --rail-width: 64px; grid-template-columns: var(--rail-width) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) clamp(260px, 34dvh, 320px);