fix: support direct help route

This commit is contained in:
Code Queue Review
2026-05-23 02:27:05 +00:00
parent 0a7311c3c2
commit ff19c36696
5 changed files with 59 additions and 8 deletions
+1 -1
View File
@@ -468,7 +468,7 @@ async function serveCloudWeb() {
}
const routePath = url.pathname.replace(/\/+$/, "") || "/";
const relativePath = routePath === "/" || routePath === "/gate" || routePath === "/diagnostics/gate" ? "index.html" : url.pathname.slice(1);
const relativePath = routePath === "/" || routePath === "/gate" || routePath === "/diagnostics/gate" || routePath === "/help" ? "index.html" : url.pathname.slice(1);
for (const root of roots) {
const candidate = path.resolve(root, relativePath);
if (!candidate.startsWith(root)) continue;
+46 -5
View File
@@ -64,6 +64,7 @@ const diagnosticsRequiredTerms = Object.freeze([
]);
const gateRouteAliases = Object.freeze(["/gate", "/diagnostics/gate"]);
const helpRouteAliases = Object.freeze(["/help"]);
const incompletePrimaryNavLabels = Object.freeze(["台", "证", "诊", "帮"]);
@@ -253,6 +254,11 @@ function runStaticSmoke() {
evidence: gateRouteAliases
});
addCheck(checks, blockers, "direct-help-route-alias", helpRouteAliasesAreServed(files.app, artifactPublisher, buildScript, distContractScript), "/help is a direct user-facing help alias and does not become the default route.", {
blocker: "runtime_blocker",
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 trace/evidence columns.", {
blocker: "contract_blocker",
evidence: requiredWiringColumns
@@ -966,6 +972,19 @@ function gateRouteAliasesAreServed(app, artifactPublisher, buildScript, distCont
);
}
function helpRouteAliasesAreServed(app, artifactPublisher, buildScript, distContractScript) {
const routeBody = functionBody(app, "routeFromLocation");
return (
/helpPathnames\(\)\.has/u.test(routeBody) &&
helpRouteAliases.every((route) => app.includes(`"${route}"`)) &&
helpRouteAliases.every((route) => artifactPublisher.includes(`routePath === "${route}"`)) &&
/buildCloudWebDist/u.test(buildScript) &&
/help\/index\.html/u.test(distContractScript) &&
/return "help"/u.test(routeBody) &&
finalRouteFallback(app) === "workspace"
);
}
function wiringTableContract(html) {
const wiringPanel = html.match(/<section\b[^>]*\bdata-side-panel=["']wiring["'][\s\S]*?<\/section>/u)?.[0] ?? "";
return requiredWiringColumns.every((column) => wiringPanel.includes(`<th>${column}</th>`));
@@ -1321,7 +1340,7 @@ function inspectHelpRoute(html, app) {
/data-route=["']help["']/u.test(html) ||
/data-view=["']help["']/u.test(html) ||
/#help\b/u.test(html + app) ||
/pathname[\s\S]{0,160}\/help/u.test(app);
/pathname[\s\S]{0,220}\/help/u.test(app);
const finalFallback = finalRouteFallback(app);
const helpViewHidden = helpViewTag ? /\bhidden\b/u.test(helpViewTag) : false;
return {
@@ -1983,6 +2002,12 @@ export async function runDevCloudWorkbenchMobileSmoke() {
await page.click('[data-route="help"]');
await page.waitForFunction(() => document.querySelector("#help-content")?.dataset.helpState === "ready", null, { timeout: 8000 });
const help = await inspectHelpMarkdownRoute(page);
await page.goto(new URL("/#/help", server.url).toString(), { waitUntil: "networkidle", timeout: 15000 });
await page.waitForFunction(() => document.querySelector("#help-content")?.dataset.helpState === "ready", null, { timeout: 8000 });
const slashHashHelp = await inspectHelpMarkdownRoute(page);
await page.goto(new URL("/help", server.url).toString(), { waitUntil: "networkidle", timeout: 15000 });
await page.waitForFunction(() => document.querySelector("#help-content")?.dataset.helpState === "ready", null, { timeout: 8000 });
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");
@@ -2018,10 +2043,22 @@ export async function runDevCloudWorkbenchMobileSmoke() {
},
{
id: "mobile-help-markdown-route",
status: help.nonDefaultMarkdownHelp && help.termsPresent ? "pass" : "blocked",
summary: "Help opens as a non-default internal route rendered from help.md by the vendored Markdown renderer.",
status: help.nonDefaultMarkdownHelp && help.routeIsHashHelp && help.termsPresent ? "pass" : "blocked",
summary: "Help opens from the rail as a non-default hash route rendered from help.md by the vendored Markdown renderer.",
observations: help
},
{
id: "mobile-slash-hash-help-route",
status: slashHashHelp.nonDefaultMarkdownHelp && slashHashHelp.routeIsSlashHashHelp && slashHashHelp.termsPresent ? "pass" : "blocked",
summary: "Direct #/help opens the user-facing help view.",
observations: slashHashHelp
},
{
id: "mobile-direct-help-route",
status: directHelp.nonDefaultMarkdownHelp && directHelp.routeIsDirectHelp && directHelp.termsPresent ? "pass" : "blocked",
summary: "Direct /help opens the user-facing help view instead of raw API 404 JSON.",
observations: directHelp
},
{
id: "mobile-default-hit-test",
status: closed.primaryControlsReachable ? "pass" : "blocked",
@@ -2109,7 +2146,7 @@ async function startStaticWebServer(options = {}) {
return;
}
let pathname = decodeURIComponent(url.pathname);
if (pathname === "/" || pathname === "/workbench" || pathname === "/gate" || pathname === "/diagnostics/gate") {
if (pathname === "/" || pathname === "/workbench" || pathname === "/gate" || pathname === "/diagnostics/gate" || pathname === "/help") {
pathname = "/index.html";
}
const filePath = path.resolve(webRoot, pathname.replace(/^\/+/, ""));
@@ -2490,6 +2527,7 @@ async function inspectHelpMarkdownRoute(page) {
document.body.scrollHeight <= document.body.clientHeight + 2;
return {
hash: window.location.hash,
pathname: window.location.pathname,
helpState: helpContent?.dataset.helpState ?? null,
workspaceHidden: workspace ? workspace.hidden : null,
gateHidden: gate ? gate.hidden : null,
@@ -2504,8 +2542,11 @@ async function inspectHelpMarkdownRoute(page) {
helpPanelScrolls: Boolean(helpScroll && helpScroll.scrollHeight > helpScroll.clientHeight && helpScroll.after > helpScroll.before),
rootStillLocked,
helpScroll,
routeIsHashHelp: window.location.hash === "#help",
routeIsSlashHashHelp: window.location.hash === "#/help",
routeIsDirectHelp: (window.location.pathname.replace(/\/+$/, "") || "/") === "/help",
nonDefaultMarkdownHelp:
window.location.hash === "#help" &&
(window.location.hash === "#help" || window.location.hash === "#/help" || (window.location.pathname.replace(/\/+$/, "") || "/") === "/help") &&
workspace?.hidden === true &&
gate?.hidden === true &&
help?.hidden === false &&
+6 -1
View File
@@ -136,8 +136,9 @@ function initRoutes() {
}
function routeFromLocation() {
const hashRoute = window.location.hash.replace(/^#/, "");
const hashRoute = window.location.hash.replace(/^#\/?/, "");
if (viewIds.has(hashRoute)) return hashRoute;
if (helpPathnames().has(window.location.pathname.replace(/\/+$/, "") || "/")) return "help";
if (internalGatePathnames().has(window.location.pathname.replace(/\/+$/, "") || "/")) return "gate";
return "workspace";
}
@@ -1822,6 +1823,10 @@ function internalGatePathnames() {
return new Set(["/gate", "/diagnostics/gate"]);
}
function helpPathnames() {
return new Set(["/help"]);
}
function userFacingSummary(text) {
return String(text ?? "")
.replaceAll("Public DEV route/frp", "公共 DEV 路由/frp")
+5
View File
@@ -217,6 +217,9 @@ assert.match(app, /marked\.parse\(/);
assert.doesNotMatch(app, /function\s+parseMarkdown|markedRegex|markdownRegex/);
assert.match(app, /fetch\("\/help\.md"/);
assert.match(app, /帮助内容加载失败/);
assert.match(functionBody(app, "routeFromLocation"), /replace\(\/\^#\\\/\?\//);
assert.match(functionBody(app, "routeFromLocation"), /helpPathnames\(\)\.has/);
assert.match(functionBody(app, "routeFromLocation"), /return "help"/);
assert.match(app, /return "workspace";/);
assert.match(styles, /\.help-view\s*{[^}]*overflow:\s*hidden;/s);
assert.match(styles, /\.help-panel\s*{[^}]*overflow:\s*hidden;/s);
@@ -283,6 +286,7 @@ assert.match(distContractScript, /"third_party\/marked\/marked\.esm\.js"/);
assert.match(distContractScript, /"third_party\/marked\/LICENSE"/);
assert.match(distContractScript, /gate\/index\.html/);
assert.match(distContractScript, /diagnostics\/gate\/index\.html/);
assert.match(distContractScript, /help\/index\.html/);
assert.match(markedLicense, /MarkedJS/);
assert.match(markedLicense, /Permission is hereby granted, free of charge/);
assert.match(html, /live-status/);
@@ -395,6 +399,7 @@ assert.match(artifactPublisher, /cloud_api_proxy_failed/);
assert.match(artifactPublisher, /url\.pathname\.replace\(\/\\\/\+\$\/,\s*""\) \|\| "\/"/);
assert.match(artifactPublisher, /routePath === "\/gate"/);
assert.match(artifactPublisher, /routePath === "\/diagnostics\/gate"/);
assert.match(artifactPublisher, /routePath === "\/help"/);
assert.match(app, /degraded/);
assert.match(app, /runtime\.serviceRoute\.join\(" -> "\)/);
assert.doesNotMatch(app, /callRpc\("hardware\./);
@@ -16,7 +16,7 @@ export const cloudWebDistRuntimeFiles = Object.freeze([
"third_party/marked/marked.esm.js",
"third_party/marked/LICENSE"
]);
export const cloudWebDistAliasFiles = Object.freeze(["gate/index.html", "diagnostics/gate/index.html"]);
export const cloudWebDistAliasFiles = Object.freeze(["gate/index.html", "diagnostics/gate/index.html", "help/index.html"]);
function absolutePath(rootDir, relativePath) {
return path.resolve(rootDir, relativePath);