From 02ae48835bfa05dfc1d9c6bb52113d5951125442 Mon Sep 17 00:00:00 2001 From: Code Queue Review Date: Sat, 23 May 2026 14:05:18 +0000 Subject: [PATCH] fix: guard workbench wiring layout --- scripts/src/dev-cloud-workbench-smoke-lib.mjs | 70 +++++++++++++++++-- web/hwlab-cloud-web/scripts/check.mjs | 10 +++ web/hwlab-cloud-web/styles.css | 9 ++- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/scripts/src/dev-cloud-workbench-smoke-lib.mjs b/scripts/src/dev-cloud-workbench-smoke-lib.mjs index a0b41419..977106ef 100644 --- a/scripts/src/dev-cloud-workbench-smoke-lib.mjs +++ b/scripts/src/dev-cloud-workbench-smoke-lib.mjs @@ -1078,9 +1078,21 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) { { id: "layout-issue-287-future-hardware-status-tabs", status: allViewportCoverage(viewportResults, "issue287HardwareTabsReady") ? "pass" : "skip", - summary: "#287 hardware status tab set is not required for this #273 smoke until the wider live hardware workspace is implemented; current visible right-side containers are still checked for overflow.", + summary: "#287 hardware status tabs are current layout contract: desktop and 390x844 mobile viewports must expose center-hit tabs without horizontal overflow.", observations: summarizeCoverageGap(viewportResults, "issue287") }, + { + id: "layout-hardware-tabs-center-hit", + status: allViewportCoverage(viewportResults, "hardwareTabsCenterHit") ? "pass" : "blocked", + summary: "Hardware status tabs are center-hit reachable in desktop, narrow desktop, and 390x844 mobile layout checks.", + observations: summarizeCoverageGap(viewportResults, "hardwareTabs") + }, + { + id: "layout-wiring-readable-two-column", + status: allViewportCoverage(viewportResults, "wiringReadableTwoColumn") ? "pass" : "blocked", + summary: "Patch-panel wiring stays a two-column long table with DO1/DI1 text readable, non-overlapping, and horizontally contained.", + observations: summarizeCoverageGap(viewportResults, "wiring") + }, { id: "layout-issue-288-future-single-table-gate", status: allViewportCoverage(viewportResults, "issue288SingleTableReady") ? "pass" : "skip", @@ -5678,6 +5690,25 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { const wiringRows = [...document.querySelectorAll("#wiring-body tr")].map((row) => [...row.querySelectorAll("td")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "") ); + const wiringCellGeometry = [...document.querySelectorAll(".wiring-long-table th, .wiring-long-table td, #wiring-summary")].map((element) => { + const box = element.getBoundingClientRect(); + const style = getComputedStyle(element); + return { + label: element.id ? `#${element.id}` : element.tagName.toLowerCase(), + text: element.textContent?.replace(/\s+/gu, " ").trim() ?? "", + box: { left: box.left, top: box.top, right: box.right, bottom: box.bottom, width: box.width, height: box.height }, + scrollWidth: element.scrollWidth, + clientWidth: element.clientWidth, + overflowX: style.overflowX, + wordBreak: style.wordBreak, + overflowWrap: style.overflowWrap + }; + }); + const wiringCellBoxes = wiringCellGeometry.map((item) => item.box).filter(Boolean); + const wiringTextContained = wiringCellGeometry.every((item) => item.scrollWidth <= item.clientWidth + 2); + const wiringTextNonOverlapping = wiringCellBoxes.every((box, index) => + wiringCellBoxes.every((other, otherIndex) => index === otherIndex || !boxesOverlap(box, other, -1)) + ); const wiringLegacyHeaderHits = wiringHeaders.filter((header) => wiringLegacyHeaders.includes(header)); const wiringHorizontalScroll = { panelClientWidth: wiringPanel?.clientWidth ?? 0, @@ -5730,6 +5761,17 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { wiringHorizontalScroll }); } + if (!wiringTextContained || !wiringTextNonOverlapping) { + failures.push({ + failureType: wiringTextContained ? "overlap" : "overflow", + selector: "#panel-wiring .wiring-long-table", + viewport, + summary: "#276 wiring panel text is clipped or overlaps in the two-column table/summary.", + wiringCellGeometry, + wiringTextContained, + wiringTextNonOverlapping + }); + } const controlTab = document.querySelector("#tab-control"); if (controlTab?.getAttribute("aria-selected") !== "true") { controlTab?.click(); @@ -5884,6 +5926,9 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { const issue287HardwareTabsReady = ["总览", "Gateway-SIMU", "BOX-SIMU-1", "BOX-SIMU-2", "Patch Panel"].every((label) => Boolean([...document.querySelectorAll(".hardware-status button, .hardware-status [role='tab'], .hardware-status .side-tab")].find((item) => item.textContent?.includes(label))) ); + const hardwareTabsCenterHit = keyTargets + .filter((target) => target.selector?.startsWith("[data-hardware-tab=")) + .every((target) => target.ok); if (!issue287HardwareTabsReady) { skip.push({ failureType: "skip", @@ -5910,7 +5955,10 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { const passWithWiring = pass && wiringLongTableOk && - wiringNoHorizontalScroll; + wiringNoHorizontalScroll && + wiringTextContained && + wiringTextNonOverlapping && + hardwareTabsCenterHit; return { mode, viewport, @@ -5940,14 +5988,19 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) { blockedDrawerTargets, sidePanelScroll, sidePanelOverflowUsable, + hardwareTabsCenterHit, wiring: { longTableOk: wiringLongTableOk, noHorizontalScroll: wiringNoHorizontalScroll, + textContained: wiringTextContained, + textNonOverlapping: wiringTextNonOverlapping, headers: wiringHeaders, legacyHeaderHits: wiringLegacyHeaderHits, rows: wiringRows, - horizontalScroll: wiringHorizontalScroll + horizontalScroll: wiringHorizontalScroll, + cellGeometry: wiringCellGeometry }, + wiringReadableTwoColumn: wiringLongTableOk && wiringNoHorizontalScroll && wiringTextContained && wiringTextNonOverlapping, semanticOverlapChecks, overflowChecks, defaultCollapsedMobile, @@ -6218,7 +6271,12 @@ function allViewportCoverage(viewportResults, fieldName) { } function summarizeCoverageGap(viewportResults, issueKey) { - const fieldName = issueKey === "issue288" ? "issue288SingleTableReady" : "issue287HardwareTabsReady"; + const fieldName = { + issue288: "issue288SingleTableReady", + issue287: "issue287HardwareTabsReady", + hardwareTabs: "hardwareTabsCenterHit", + wiring: "wiringReadableTwoColumn" + }[issueKey] ?? "issue287HardwareTabsReady"; return { status: allViewportCoverage(viewportResults, fieldName) ? "pass" : "skip", coverage: Object.fromEntries( @@ -6234,7 +6292,9 @@ function summarizeCoverageGap(viewportResults, issueKey) { ...(result.gate?.currentRoute?.skip ?? []) ].filter((item) => issueKey === "issue288" ? item.summary?.includes("#288") - : item.summary?.includes("#287")) + : issueKey === "issue287" + ? item.summary?.includes("#287") + : false) } ]) ) diff --git a/web/hwlab-cloud-web/scripts/check.mjs b/web/hwlab-cloud-web/scripts/check.mjs index c48ac1aa..d38e0c13 100644 --- a/web/hwlab-cloud-web/scripts/check.mjs +++ b/web/hwlab-cloud-web/scripts/check.mjs @@ -144,6 +144,16 @@ assert.equal( "pass", "#287 hardware status tabs must remain covered by layout smoke" ); +assert.equal( + layoutSmoke.checks.find((check) => check.id === "layout-hardware-tabs-center-hit")?.status, + "pass", + "hardware status tabs must be center-hit reachable on desktop and 390x844 mobile" +); +assert.equal( + layoutSmoke.checks.find((check) => check.id === "layout-wiring-readable-two-column")?.status, + "pass", + "#276 wiring table must stay readable, non-overlapping, and horizontally contained" +); assert.equal( layoutSmoke.checks.find((check) => check.id === "layout-issue-288-future-single-table-gate")?.status, "skip", diff --git a/web/hwlab-cloud-web/styles.css b/web/hwlab-cloud-web/styles.css index fdf8108a..2492f324 100644 --- a/web/hwlab-cloud-web/styles.css +++ b/web/hwlab-cloud-web/styles.css @@ -1032,6 +1032,11 @@ h3 { grid-template-rows: auto auto minmax(0, 1fr); } +#panel-wiring { + grid-template-rows: auto auto auto; + align-content: start; +} + .m3-control-form { min-width: 0; display: grid; @@ -1188,8 +1193,10 @@ table { .wiring-table-wrap { width: 100%; min-width: 0; + min-height: 128px; + max-height: min(220px, 42dvh); overflow-x: hidden; - overflow-y: visible; + overflow-y: auto; } .wiring-long-table {