fix: clean workbench first viewport labels
This commit is contained in:
@@ -72,6 +72,9 @@ const defaultHomepageForbiddenTerms = Object.freeze([
|
||||
"验收",
|
||||
"M0-M5",
|
||||
"BLOCKED",
|
||||
"SOURCE",
|
||||
"DRY-RUN",
|
||||
"DEV-LIVE",
|
||||
"db-live",
|
||||
"m3-hardware-loop-runtime",
|
||||
"执行轨迹",
|
||||
@@ -133,7 +136,7 @@ const requiredHardwareStatusTerms = Object.freeze([
|
||||
"Patch Panel 连线",
|
||||
"DO1 -> patch-panel -> DI1",
|
||||
"AI/AO/FREQ",
|
||||
"future capability",
|
||||
"后续能力",
|
||||
"SOURCE",
|
||||
"DEV-LIVE",
|
||||
"BLOCKED"
|
||||
@@ -1109,14 +1112,12 @@ function beforeSection(source, viewId) {
|
||||
}
|
||||
|
||||
function defaultHomepageHidesBackstage({ html }) {
|
||||
const workspaceHtml = sectionSource(html, "workspace");
|
||||
const visibleShellHtml = `${beforeSection(html, "help")}\n${workspaceHtml}`;
|
||||
return defaultHomepageForbiddenTerms.every((term) => !visibleShellHtml.includes(term));
|
||||
const firstViewportText = visibleTextFromHtml(defaultFirstViewportHtml(html));
|
||||
return defaultHomepageForbiddenTerms.every((term) => !textHasForbiddenTerm(firstViewportText, term));
|
||||
}
|
||||
|
||||
function defaultWorkspaceIsSanitized({ html, app }) {
|
||||
const workspaceHtml = sectionSource(html, "workspace");
|
||||
const defaultShell = `${beforeSection(html, "help")}\n${workspaceHtml}`;
|
||||
const defaultShellText = visibleTextFromHtml(defaultFirstViewportHtml(html));
|
||||
const userFacingBodies = [
|
||||
functionBody(app, "codeAgentStatusMessage"),
|
||||
functionBody(app, "codeAgentPromptText"),
|
||||
@@ -1127,14 +1128,47 @@ function defaultWorkspaceIsSanitized({ html, app }) {
|
||||
functionBody(app, "renderWiringList")
|
||||
].join("\n");
|
||||
return (
|
||||
!/OPENAI_API_KEY|hwlab-code-agent-provider|Secret|secretRef|m3-route-required|waiting-for-dev-live/u.test(defaultShell) &&
|
||||
!/OPENAI_API_KEY|hwlab-code-agent-provider|Secret|secretRef|m3-route-required|waiting-for-dev-live|BLOCKED 凭证缺口/u.test(userFacingBodies) &&
|
||||
defaultHomepageForbiddenTerms.every((term) => !textHasForbiddenTerm(defaultShellText, term)) &&
|
||||
!/OPENAI_API_KEY|hwlab-code-agent-provider|Secret|secretRef|m3-route-required|waiting-for-dev-live|BLOCKED 凭证缺口|SOURCE fixture|DEV-LIVE/u.test(userFacingBodies) &&
|
||||
/服务受阻/u.test(functionBody(app, "renderAgentChatStatus")) &&
|
||||
/Code Agent 服务暂不可用/u.test(functionBody(app, "codeAgentBlockedSummary")) &&
|
||||
/等待可信记录/u.test(functionBody(app, "renderWiringList"))
|
||||
);
|
||||
}
|
||||
|
||||
function defaultFirstViewportHtml(html) {
|
||||
return `${beforeSection(html, "help")}\n${rightSidebarDefaultHtml(html)}`;
|
||||
}
|
||||
|
||||
function rightSidebarDefaultHtml(html) {
|
||||
const start = html.search(/<aside\b[^>]*\bclass=["'][^"']*\bright-sidebar\b[^"']*["'][^>]*>/u);
|
||||
if (start === -1) return "";
|
||||
const wiringStart = html.slice(start).search(/<section\b[^>]*\bid=["']panel-wiring["'][^>]*>/u);
|
||||
if (wiringStart !== -1) return html.slice(start, start + wiringStart);
|
||||
const end = html.slice(start).search(/<\/aside>/u);
|
||||
return end === -1 ? html.slice(start) : html.slice(start, start + end);
|
||||
}
|
||||
|
||||
function visibleTextFromHtml(fragment) {
|
||||
return fragment
|
||||
.replace(/<script\b[\s\S]*?<\/script>/giu, " ")
|
||||
.replace(/<style\b[\s\S]*?<\/style>/giu, " ")
|
||||
.replace(/<[^>]+>/gu, " ")
|
||||
.replace(/ /gu, " ")
|
||||
.replace(/>/gu, ">")
|
||||
.replace(/</gu, "<")
|
||||
.replace(/&/gu, "&")
|
||||
.replace(/\s+/gu, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function textHasForbiddenTerm(text, term) {
|
||||
if (/^[A-Za-z][A-Za-z0-9-]*$/u.test(term)) {
|
||||
return new RegExp(`\\b${escapeRegExp(term)}\\b`, "u").test(text);
|
||||
}
|
||||
return text.includes(term);
|
||||
}
|
||||
|
||||
function hasCompletePrimaryNavigation(html) {
|
||||
const navLabelsPresent = ["工作台", "内部复核", "使用说明", "资源树"].every((label) => html.includes(`>${label}<`));
|
||||
const legacyLabelsAbsent = incompletePrimaryNavLabels.every((label) => !html.includes(`>${label}<`));
|
||||
@@ -1190,7 +1224,7 @@ function hardwareStatusStructureContract({ html, app, styles }) {
|
||||
/function\s+hasTrustedM3LiveEvidence\s*\(/u.test(app) &&
|
||||
/function\s+linkStatusLabel\s*\(/u.test(app) &&
|
||||
/function\s+normalizePort\s*\(/u.test(app) &&
|
||||
/Web 不提供硬件写 RPC;不会新增伪硬件写操作。/u.test(app) &&
|
||||
/页面不直连模拟器,也不伪造硬件状态。/u.test(app) &&
|
||||
/\.hardware-section\s*\{/u.test(styles) &&
|
||||
/\.hardware-row\s*\{/u.test(styles)
|
||||
);
|
||||
@@ -1370,8 +1404,8 @@ function hasCodeAgentReadinessVisibility({ html, app }) {
|
||||
blockedCodeAgentUiLabels.some((label) => app.includes(label)) &&
|
||||
/provider_unavailable/u.test(app) &&
|
||||
/OPENAI_API_KEY/u.test(app) &&
|
||||
/只有真实 completed 回复才标 DEV-LIVE/u.test(app) &&
|
||||
/不能因为只有 conversationId 标为开发实况/u.test(app) &&
|
||||
/只有真实完成回复才显示为完成/u.test(app) &&
|
||||
/不能因为只有会话编号就当成实况完成/u.test(app) &&
|
||||
/isTrustedCodeAgentProvider/u.test(app) &&
|
||||
/Boolean\(value\?\.model\)/u.test(app) &&
|
||||
/Boolean\(value\?\.backend\)/u.test(app) &&
|
||||
@@ -1492,8 +1526,8 @@ function m3FixtureCountsNotDevLive(app) {
|
||||
/hasTrustedPatchPanelLiveReport/u.test(liveEvidenceBody) &&
|
||||
/serviceId\s*!==\s*M3_TRUSTED_ROUTE\.patchPanelServiceId/u.test(patchPanelReportBody) &&
|
||||
/trustedM3Link\(\[link\]\)/u.test(patchPanelReportBody) &&
|
||||
/counts 不是 patch-panel live report/u.test(linkDetailBody) &&
|
||||
/SOURCE fixture 包含目标接线/u.test(linkDetailBody)
|
||||
/只读运行计数已返回,但还不能证明接线盘闭环/u.test(linkDetailBody) &&
|
||||
/来源拓扑包含目标接线/u.test(linkDetailBody)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1869,7 +1903,7 @@ async function inspectLiveDom(url) {
|
||||
browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1366, height: 768 } });
|
||||
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 8000 });
|
||||
const dom = await page.evaluate(async () => {
|
||||
const dom = await page.evaluate(async (forbiddenTerms) => {
|
||||
const workspace = document.querySelector('[data-view="workspace"]');
|
||||
const gate = document.querySelector('[data-view="gate"]');
|
||||
const help = document.querySelector('[data-view="help"]');
|
||||
@@ -1884,10 +1918,34 @@ async function inspectLiveDom(url) {
|
||||
const box = element.getBoundingClientRect();
|
||||
return style.visibility !== "hidden" && style.display !== "none" && box.width > 0 && box.height > 0;
|
||||
};
|
||||
const visibleTextInFirstViewport = () => {
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
||||
const chunks = [];
|
||||
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||
const textContent = node.textContent?.replace(/\s+/gu, " ").trim();
|
||||
if (!textContent) continue;
|
||||
const parent = node.parentElement;
|
||||
if (!parent) continue;
|
||||
const style = getComputedStyle(parent);
|
||||
if (style.visibility === "hidden" || style.display === "none") continue;
|
||||
const box = parent.getBoundingClientRect();
|
||||
const inViewport = box.width > 0 && box.height > 0 && box.bottom > 0 && box.right > 0 && box.top < window.innerHeight && box.left < window.innerWidth;
|
||||
if (inViewport) chunks.push(textContent);
|
||||
}
|
||||
return chunks.join(" ").replace(/\s+/gu, " ").trim();
|
||||
};
|
||||
const textHasForbiddenTerm = (candidateText, term) => {
|
||||
if (/^[A-Za-z][A-Za-z0-9-]*$/u.test(term)) {
|
||||
const escaped = term.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
||||
return new RegExp(`\\b${escaped}\\b`, "u").test(candidateText);
|
||||
}
|
||||
return candidateText.includes(term);
|
||||
};
|
||||
window.scrollTo(0, 240);
|
||||
document.documentElement.scrollTop = 240;
|
||||
document.body.scrollTop = 240;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
const firstViewportText = visibleTextInFirstViewport();
|
||||
const rootAfterScrollAttempt = {
|
||||
windowScrollY: window.scrollY,
|
||||
htmlScrollTop: document.documentElement.scrollTop,
|
||||
@@ -1903,6 +1961,8 @@ async function inspectLiveDom(url) {
|
||||
diagnosticsHidden: diagnostics ? diagnostics.hidden : null,
|
||||
outerScrollLocked: document.documentElement.scrollHeight <= document.documentElement.clientHeight + 2,
|
||||
rootAfterScrollAttempt,
|
||||
firstViewportForbiddenPresent: forbiddenTerms.filter((term) => textHasForbiddenTerm(firstViewportText, term)),
|
||||
firstViewportTextSample: firstViewportText.slice(0, 400),
|
||||
labelsPresent: ["硬件资源", "Agent 对话", "工作清单", "可信记录", "使用说明"].every((label) => text.includes(label)),
|
||||
coreControlsVisible: {
|
||||
commandInput: visible("#command-input"),
|
||||
@@ -1914,7 +1974,7 @@ async function inspectLiveDom(url) {
|
||||
recordsTab: visible("#tab-records")
|
||||
}
|
||||
};
|
||||
});
|
||||
}, [...defaultHomepageForbiddenTerms]);
|
||||
const pass =
|
||||
dom.title === "HWLAB 云工作台" &&
|
||||
dom.bodyOverflow === "hidden" &&
|
||||
@@ -1927,12 +1987,18 @@ async function inspectLiveDom(url) {
|
||||
dom.rootAfterScrollAttempt.windowScrollY === 0 &&
|
||||
dom.rootAfterScrollAttempt.htmlScrollTop === 0 &&
|
||||
dom.rootAfterScrollAttempt.bodyScrollTop === 0 &&
|
||||
dom.firstViewportForbiddenPresent.length === 0 &&
|
||||
dom.labelsPresent &&
|
||||
Object.values(dom.coreControlsVisible).every(Boolean);
|
||||
return {
|
||||
status: pass ? "pass" : "blocked",
|
||||
summary: pass ? "Browser DOM confirms the default workbench, core controls, and outer scroll lock." : "Browser DOM did not satisfy the workbench contract.",
|
||||
evidence: [`title=${dom.title}`, `bodyOverflow=${dom.bodyOverflow}`, `htmlOverflow=${dom.htmlOverflow}`],
|
||||
evidence: [
|
||||
`title=${dom.title}`,
|
||||
`bodyOverflow=${dom.bodyOverflow}`,
|
||||
`htmlOverflow=${dom.htmlOverflow}`,
|
||||
`firstViewportForbidden=${dom.firstViewportForbiddenPresent.join(",") || "none"}`
|
||||
],
|
||||
observations: dom
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user