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);