fix: render patch panel wiring as long table

This commit is contained in:
Code Queue Review
2026-05-23 08:48:46 +00:00
parent 7f22016c90
commit c18c141c23
10 changed files with 334 additions and 71 deletions
@@ -200,6 +200,17 @@ test("source/default smoke covers #278 sidebar collapse static contract", () =>
assert.equal(report.checks.find((check) => check.id === "feedback-278-sidebar-collapse-contract")?.status, "pass");
});
test("source/default smoke covers #276 two-column wiring long-table contract", () => {
const report = runDevCloudWorkbenchStaticSmoke();
const check = report.checks.find((item) => item.id === "feedback-276-wiring-long-table");
assert.equal(check?.status, "pass");
assert.deepEqual(
check.evidence.filter((item) => ["res_boxsimu_1", "res_boxsimu_2", "DO1 对 DI1"].includes(item)),
["res_boxsimu_1", "res_boxsimu_2", "DO1 对 DI1"]
);
assert.equal(check.evidence.includes("legacy 8-column table forbidden"), true);
});
test("live workbench identity passes only when runtime commit or image tag matches current source", () => {
assert.equal(
classifyLiveDeploymentIdentity(sourceIdentity, {
@@ -503,11 +514,18 @@ test("layout smoke verifies desktop and mobile sidebar collapse geometry", async
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);
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);
const desktopExpanded = report.checks.find((check) => check.id === "layout-desktop-expanded")?.observations;
assert.equal(desktopExpanded.failures.length, 0);
+150 -19
View File
@@ -102,7 +102,8 @@ const helpRouteAliases = Object.freeze(["/help"]);
const incompletePrimaryNavLabels = Object.freeze(["台", "证", "诊", "帮"]);
const requiredWiringColumns = Object.freeze([
const requiredWiringLongTableHeaders = Object.freeze(["res_boxsimu_1", "res_boxsimu_2"]);
const forbiddenLegacyWiringColumns = Object.freeze([
"源设备",
"源端口",
"接线盘",
@@ -112,6 +113,7 @@ const requiredWiringColumns = Object.freeze([
"证据来源",
"轨迹/证据"
]);
const requiredWiringSummaryTerms = Object.freeze(["hwlab-patch-panel", "状态", "证据来源", "轨迹/证据"]);
const requiredTrustedRecordTerms = Object.freeze([
"Code Agent 对话记录",
@@ -367,9 +369,15 @@ function runStaticSmoke() {
evidence: [...helpRouteAliases, "routeFromLocation final fallback is workspace"]
});
addCheck(checks, blockers, "feedback-120-wiring-table", wiringTableContract(files.html), "Patch-panel wiring panel is a table with source, target, status, evidence source, and 轨迹/证据 columns.", {
addCheck(checks, blockers, "feedback-276-wiring-long-table", wiringTableContract(files.html, files.styles, files.app), "Patch-panel wiring panel is a two-column long table: device headers first, IO mapping rows after, and metadata moved to Chinese summary/detail text.", {
blocker: "contract_blocker",
evidence: requiredWiringColumns
evidence: [
...requiredWiringLongTableHeaders,
"DO1 对 DI1",
"data-wiring-layout=\"two-column-long-table\"",
"legacy 8-column table forbidden",
...requiredWiringSummaryTerms
]
});
addCheck(checks, blockers, "trusted-record-groups", trustedRecordGroups(files), "Trusted Records panel groups Code Agent, wiring/operation, audit, and evidence summaries with source labels and live-failure fallback.", {
@@ -1032,9 +1040,9 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
evidenceLevel: useLiveUrl ? blockers.length === 0 ? "DEV-LIVE-LAYOUT" : "BLOCKED" : "SOURCE",
devLive: false,
summary: useLiveUrl
? "Live browser layout smoke verifies workbench layout, hit targets, overflow, and /gate usability only; it is not M3 hardware acceptance."
: "Static local browser layout smoke verifies workbench layout, hit targets, overflow, and /gate usability only; deployment still requires DEV live verification.",
refs: ["pikasTech/HWLAB#273", "pikasTech/HWLAB#278", "pikasTech/HWLAB#287", "pikasTech/HWLAB#288", "pikasTech/HWLAB#99", "pikasTech/HWLAB#227"],
? "Live browser layout smoke verifies workbench layout, hit targets, overflow, /gate usability, and the two-column wiring panel only; it is not M3 hardware acceptance."
: "Static local browser layout smoke verifies workbench layout, hit targets, overflow, /gate usability, and the two-column wiring panel only; deployment still requires DEV live verification.",
refs: ["pikasTech/HWLAB#273", "pikasTech/HWLAB#276", "pikasTech/HWLAB#278", "pikasTech/HWLAB#287", "pikasTech/HWLAB#288", "pikasTech/HWLAB#99", "pikasTech/HWLAB#227"],
viewports: layoutViewports.map(({ id, width, height }) => ({ id, width, height })),
checks,
blockers,
@@ -1192,7 +1200,8 @@ function baseReport({
"pikasTech/HWLAB#131",
"pikasTech/HWLAB#143",
"pikasTech/HWLAB#164",
"pikasTech/HWLAB#224"
"pikasTech/HWLAB#224",
"pikasTech/HWLAB#276"
],
mode,
url,
@@ -1609,8 +1618,7 @@ function defaultWorkspaceIsSanitized({ html, app }) {
functionBody(app, "codeAgentControlSummary"),
functionBody(app, "codeAgentBlockedSummary"),
functionBody(app, "codeAgentAvailabilitySummary"),
functionBody(app, "failureMessage"),
functionBody(app, "renderWiringList")
functionBody(app, "failureMessage")
].join("\n");
return (
defaultHomepageForbiddenTerms.every((term) => !textHasForbiddenTerm(defaultShellText, term)) &&
@@ -1694,9 +1702,31 @@ function helpRouteAliasesAreServed(app, artifactPublisher, buildScript, distCont
);
}
function wiringTableContract(html) {
function wiringTableContract(html, styles = "", app = "") {
const wiringPanel = html.match(/<section\b[^>]*\bdata-side-panel=["']wiring["'][\s\S]*?<\/section>/u)?.[0] ?? "";
return requiredWiringColumns.every((column) => wiringPanel.includes(`<th>${column}</th>`));
const headerCount = (wiringPanel.match(/<th\b/gu) ?? []).length;
const bodyRow = wiringPanel.match(/<tr\b[^>]*\bdata-wiring-row=["']io["'][\s\S]*?<\/tr>/u)?.[0] ?? "";
const bodyCellCount = (bodyRow.match(/<td\b/gu) ?? []).length;
const renderWiringBody = functionBody(app, "renderWiringList");
return (
/data-wiring-layout=["']two-column-long-table["']/u.test(wiringPanel) &&
/class=["'][^"']*\bwiring-long-table\b/u.test(wiringPanel) &&
headerCount === 2 &&
bodyCellCount === 2 &&
requiredWiringLongTableHeaders.every((header) => wiringPanel.includes(header)) &&
["DO1", "DI1"].every((port) => wiringPanel.includes(port)) &&
requiredWiringSummaryTerms.every((term) => wiringPanel.includes(term)) &&
forbiddenLegacyWiringColumns.every((column) => !wiringPanel.includes(`<th>${column}</th>`)) &&
/id=["']wiring-summary["']/u.test(wiringPanel) &&
/overflow-x:\s*hidden/u.test(styles.match(/\.wiring-table-wrap\s*\{[\s\S]*?\}/u)?.[0] ?? "") &&
!/\.wiring-table\s*\{[\s\S]*?min-width:\s*9\d\dpx/u.test(styles) &&
!/min-width:\s*980px/u.test(styles) &&
/table-layout:\s*fixed/u.test(styles.match(/\.wiring-long-table\s*\{[\s\S]*?\}/u)?.[0] ?? "") &&
/M3_TRUSTED_ROUTE\.fromResourceId/u.test(renderWiringBody) &&
/M3_TRUSTED_ROUTE\.toResourceId/u.test(renderWiringBody) &&
/M3_TRUSTED_ROUTE\.patchPanelServiceId/u.test(renderWiringBody) &&
!/tr\.append\(cell\(M3_TRUSTED_ROUTE\.fromResourceId/u.test(renderWiringBody)
);
}
function hardwareStatusStructureContract({ html, app, styles }) {
@@ -2488,10 +2518,20 @@ async function inspectLiveDom(url, options = {}) {
),
defaultBackstageHidden: !/(Gate|诊断|验收|BLOCKED|M0-M5|执行轨迹)/u.test(document.querySelector(".center-workspace")?.innerText ?? ""),
gateRouteAvailable: [...document.querySelectorAll("[data-route='gate']")].some((button) => button.textContent?.trim() === "内部复核"),
wiringHeaders: [...document.querySelectorAll(".wiring-table thead th")].map((cell) => cell.textContent?.trim() ?? ""),
wiringLayout: document.querySelector(".wiring-table-wrap")?.getAttribute("data-wiring-layout") ?? "",
wiringHeaderDevices: [
document.querySelector("#wiring-source-device")?.textContent?.trim() ?? "",
document.querySelector("#wiring-target-device")?.textContent?.trim() ?? ""
],
wiringHeaderCount: document.querySelectorAll(".wiring-table thead th").length,
legacyWiringHeaders: [...document.querySelectorAll(".wiring-table thead th")].map((cell) => cell.textContent?.trim() ?? "").filter((text) =>
["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据"].includes(text)
),
m3WiringRows: [...document.querySelectorAll("#wiring-body tr")].map((row) =>
[...row.querySelectorAll("td")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "")
),
wiringBodyCellCounts: [...document.querySelectorAll("#wiring-body tr")].map((row) => row.querySelectorAll("td").length),
wiringSummaryText: document.querySelector("#wiring-summary")?.textContent?.replace(/\s+/gu, " ").trim() ?? "",
coreControlsVisible: {
commandInput: visible("#command-input"),
commandSend: visible("#command-send"),
@@ -2520,14 +2560,20 @@ async function inspectLiveDom(url, options = {}) {
dom.navLabelsPresent &&
dom.defaultBackstageHidden &&
dom.gateRouteAvailable &&
requiredWiringColumns.every((column) => dom.wiringHeaders.includes(column)) &&
dom.wiringLayout === "two-column-long-table" &&
dom.wiringHeaderCount === 2 &&
dom.legacyWiringHeaders.length === 0 &&
requiredWiringLongTableHeaders.every((header) => dom.wiringHeaderDevices.includes(header)) &&
dom.wiringBodyCellCounts.every((count) => count === 2) &&
dom.m3WiringRows.some((row) =>
row.includes("res_boxsimu_1") &&
row.includes("DO1") &&
row.includes("hwlab-patch-panel") &&
row.includes("res_boxsimu_2") &&
row.includes("DI1")
row.length === 2 &&
row[0].includes("DO1") &&
row[0].includes("hwlab-patch-panel") &&
row[1].includes("DI1") &&
row[1].includes("证据来源") &&
row[1].includes("轨迹/证据")
) &&
requiredWiringSummaryTerms.every((term) => dom.wiringSummaryText.includes(term)) &&
(postGuard ? postGuard.attempts.length === 0 : true) &&
Object.values(dom.coreControlsVisible).every(Boolean);
return {
@@ -2539,6 +2585,9 @@ async function inspectLiveDom(url, options = {}) {
`htmlOverflow=${dom.htmlOverflow}`,
`firstViewportForbidden=${dom.firstViewportForbiddenPresent.join(",") || "none"}`,
`gateRouteAvailable=${dom.gateRouteAvailable}`,
`wiringLayout=${dom.wiringLayout}`,
`wiringHeaderDevices=${dom.wiringHeaderDevices.join("|")}`,
`wiringBodyCellCounts=${dom.wiringBodyCellCounts.join("|")}`,
`m3WiringRows=${dom.m3WiringRows.length}`,
...(postGuard ? [`codeAgentPostAttempts=${postGuard.attempts.length}`] : [])
],
@@ -4004,6 +4053,7 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
[".hardware-status", ".side-workspace", "hardware status vs side workspace"],
["#command-form", ".right-sidebar", "Code Agent command form vs right sidebar"]
];
const wiringLegacyHeaders = ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据"];
const drawerSelectorLabels = [
[".quick-actions button", "资源树快捷操作"],
["#resource-tree summary", "资源树分组"],
@@ -4309,6 +4359,75 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
const overflowOk = overflowChecks.every((check) => check.ok);
const sidePanelScroll = await scrollableProbe("#panel-control");
const sidePanelOverflowUsable = sidePanelScroll.exists && (sidePanelScroll.scrolls || sidePanelScroll.overflowY !== "hidden");
const wiringTab = document.querySelector("#tab-wiring");
if (wiringTab?.getAttribute("aria-selected") !== "true") {
wiringTab?.click();
await new Promise((resolve) => requestAnimationFrame(resolve));
}
const wiringPanel = document.querySelector("#panel-wiring");
const wiringWrap = document.querySelector(".wiring-table-wrap");
const wiringTable = document.querySelector(".wiring-long-table");
const wiringHeaders = [...document.querySelectorAll(".wiring-table thead th")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "");
const wiringRows = [...document.querySelectorAll("#wiring-body tr")].map((row) =>
[...row.querySelectorAll("td")].map((cell) => cell.textContent?.replace(/\s+/gu, " ").trim() ?? "")
);
const wiringLegacyHeaderHits = wiringHeaders.filter((header) => wiringLegacyHeaders.includes(header));
const wiringHorizontalScroll = {
panelClientWidth: wiringPanel?.clientWidth ?? 0,
panelScrollWidth: wiringPanel?.scrollWidth ?? 0,
wrapClientWidth: wiringWrap?.clientWidth ?? 0,
wrapScrollWidth: wiringWrap?.scrollWidth ?? 0,
tableClientWidth: wiringTable?.clientWidth ?? 0,
tableScrollWidth: wiringTable?.scrollWidth ?? 0,
panelOverflowX: wiringPanel ? getComputedStyle(wiringPanel).overflowX : "missing",
wrapOverflowX: wiringWrap ? getComputedStyle(wiringWrap).overflowX : "missing"
};
const wiringNoHorizontalScroll =
Boolean(wiringPanel && wiringWrap && wiringTable) &&
wiringHorizontalScroll.panelScrollWidth <= wiringHorizontalScroll.panelClientWidth + 2 &&
wiringHorizontalScroll.wrapScrollWidth <= wiringHorizontalScroll.wrapClientWidth + 2;
const wiringLongTableOk =
wiringWrap?.getAttribute("data-wiring-layout") === "two-column-long-table" &&
wiringHeaders.length === 2 &&
wiringHeaders.includes("res_boxsimu_1 互连设备 A") &&
wiringHeaders.includes("res_boxsimu_2 互连设备 B") &&
wiringLegacyHeaderHits.length === 0 &&
wiringRows.some((row) =>
row.length === 2 &&
row[0].includes("DO1") &&
row[0].includes("hwlab-patch-panel") &&
row[1].includes("DI1") &&
row[1].includes("证据来源") &&
row[1].includes("轨迹/证据")
);
if (!wiringLongTableOk) {
failures.push({
failureType: "blocked/skip",
selector: "#panel-wiring .wiring-long-table",
viewport,
summary: "#276 wiring panel is not the required two-column long table with device headers and DO1/DI1 row details.",
wiring: {
headers: wiringHeaders,
legacyHeaderHits: wiringLegacyHeaderHits,
rows: wiringRows,
layout: wiringWrap?.getAttribute("data-wiring-layout") ?? null
}
});
}
if (!wiringNoHorizontalScroll) {
failures.push({
failureType: "overflow",
selector: "#panel-wiring",
viewport,
summary: "#276 wiring panel exposes internal horizontal scroll or clipped table width.",
wiringHorizontalScroll
});
}
const controlTab = document.querySelector("#tab-control");
if (controlTab?.getAttribute("aria-selected") !== "true") {
controlTab?.click();
await new Promise((resolve) => requestAnimationFrame(resolve));
}
const toggle = {
text: toggleElement?.textContent?.replace(/\s+/gu, " ").trim() ?? "",
ariaLabel: toggleElement?.getAttribute("aria-label") ?? "",
@@ -4469,10 +4588,14 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
drawerReachable &&
defaultCollapsedMobile &&
sidePanelOverflowUsable;
const passWithWiring =
pass &&
wiringLongTableOk &&
wiringNoHorizontalScroll;
return {
mode,
viewport,
pass,
pass: passWithWiring,
explorerCollapsed,
explorerVisible,
toggle,
@@ -4492,6 +4615,14 @@ async function inspectLayoutState(page, { mode, viewport, compareTo }) {
blockedDrawerTargets,
sidePanelScroll,
sidePanelOverflowUsable,
wiring: {
longTableOk: wiringLongTableOk,
noHorizontalScroll: wiringNoHorizontalScroll,
headers: wiringHeaders,
legacyHeaderHits: wiringLegacyHeaderHits,
rows: wiringRows,
horizontalScroll: wiringHorizontalScroll
},
semanticOverlapChecks,
overflowChecks,
defaultCollapsedMobile,
+20 -8
View File
@@ -2053,22 +2053,34 @@ function assertDevCloudWorkbenchDomObservations(dom, label) {
assert.equal(dom.codeAgentPostGuard.blockedPath, "/v1/agent/chat", `${label}.codeAgentPostGuard.blockedPath`);
assert.equal(dom.codeAgentPostGuard.attemptedCount, 0, `${label}.codeAgentPostGuard.attemptedCount`);
}
assertStringArray(dom.wiringHeaders, `${label}.wiringHeaders`, { minLength: 8 });
for (const column of ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "trace/evidence"]) {
assert.ok(dom.wiringHeaders.includes(column), `${label}.wiringHeaders missing ${column}`);
assert.equal(dom.wiringLayout, "two-column-long-table", `${label}.wiringLayout`);
assertStringArray(dom.wiringHeaderDevices, `${label}.wiringHeaderDevices`, { minLength: 2 });
assert.equal(dom.wiringHeaderDevices.includes("res_boxsimu_1"), true, `${label}.wiringHeaderDevices missing res_boxsimu_1`);
assert.equal(dom.wiringHeaderDevices.includes("res_boxsimu_2"), true, `${label}.wiringHeaderDevices missing res_boxsimu_2`);
assert.equal(dom.wiringHeaderCount, 2, `${label}.wiringHeaderCount`);
assertStringArray(dom.legacyWiringHeaders, `${label}.legacyWiringHeaders`);
assert.equal(dom.legacyWiringHeaders.length, 0, `${label}.legacyWiringHeaders must not restore legacy wide columns`);
assertArray(dom.wiringBodyCellCounts, `${label}.wiringBodyCellCounts`);
for (const count of dom.wiringBodyCellCounts) {
assert.equal(count, 2, `${label}.wiringBodyCellCounts must stay two-column`);
}
assertArray(dom.m3WiringRows, `${label}.m3WiringRows`);
assert.ok(
dom.m3WiringRows.some((row) =>
Array.isArray(row) &&
row.includes("res_boxsimu_1") &&
row.includes("DO1") &&
row.includes("hwlab-patch-panel") &&
row.includes("res_boxsimu_2") &&
row.includes("DI1")
row.length === 2 &&
row[0].includes("DO1") &&
row[0].includes("hwlab-patch-panel") &&
row[1].includes("DI1") &&
row[1].includes("证据来源") &&
row[1].includes("轨迹/证据")
),
`${label}.m3WiringRows missing required M3 route`
);
assert.match(dom.wiringSummaryText, /hwlab-patch-panel/u, `${label}.wiringSummaryText missing patch-panel`);
assert.match(dom.wiringSummaryText, /状态/u, `${label}.wiringSummaryText missing status`);
assert.match(dom.wiringSummaryText, /证据来源/u, `${label}.wiringSummaryText missing evidence source`);
assert.match(dom.wiringSummaryText, /轨迹\/证据/u, `${label}.wiringSummaryText missing trace/evidence`);
assertObject(dom.coreControlsVisible, `${label}.coreControlsVisible`);
for (const control of ["commandInput", "commandSend", "agentChatStatus", "conversationList", "hardwareList", "wiringTab", "recordsTab"]) {
assert.equal(dom.coreControlsVisible[control], true, `${label}.coreControlsVisible.${control}`);