From e910d976f864253a158668d104ac2a8f1aa229cc Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 4 Apr 2026 10:45:31 +0800 Subject: [PATCH] feat: refocus governance mutations after save --- CHANGELOG.md | 7 ++++ web/storyforge-web-v4/assets/app.js | 37 ++++++++++++++++++- .../tests/workbench-pages.test.mjs | 24 ++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ec84b9..65a911f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -298,3 +298,10 @@ - 精确版本 id - 推荐业务动作 - 工作流标签 + +### 治理保存后的工作区回跳 + +- OneLiner 主配置在保存和历史回滚成功后,会自动回到 `Agent -> 当前 Agent 工作台 -> OneLiner 主 Agent` 区块。 +- 用户全局策略、用户平台策略在保存和历史回滚成功后,会自动回到 `我的策略` 对应 tab,不再只停留在成功提示里。 +- 管理员覆盖策略在保存和历史回滚成功后,会自动回到 `管理员配置台 -> 覆盖与审计`,方便连续治理和审计查看。 +- 前端回归新增了这三条治理回跳断言,避免后续又退回成“改完策略后自己重新找页面”。 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index d51cd12..1f1c4f0 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -4447,7 +4447,7 @@ function renderAdminGovernanceAuditPanel() { const targetSummary = getAdminOverrideTargetSummary(); const audits = safeArray(appState.adminPolicyAudits); return ` -
+

覆盖与审计

@@ -6588,6 +6588,7 @@ function renderPlaybookScreen() {
+

OneLiner 主 Agent

@@ -7274,7 +7275,7 @@ function renderStrategyScreen() {
` : ""}
-
+

策略治理

@@ -8858,6 +8859,14 @@ function openUploadVideoAction() { }); } +function focusPlaybookOneLinerWorkspace(anchorId = "oneliner-profile-anchor") { + appState.playbookDetailTab = "workspace"; + setScreen("playbook"); + setTimeout(() => { + document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); + }, 0); +} + function openOneLinerProfileAction() { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); @@ -8897,6 +8906,7 @@ function openOneLinerProfileAction() { await loadAgentControlSurfaces(project.id); rememberAction("OneLiner 已保存", `已更新 OneLiner「${saved.display_name || "OneLiner"}」配置,当前版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); + focusPlaybookOneLinerWorkspace("oneliner-profile-anchor"); } }); } @@ -8959,6 +8969,7 @@ async function openOneLinerProfileHistoryAction(preferredVersionId = "") { await loadAgentControlSurfaces(project.id); rememberAction("OneLiner 已回滚", `已回滚到版本 ${saved.current_version?.version_no || "指定版本"}。`, "green", saved); renderAll(); + focusPlaybookOneLinerWorkspace("oneliner-profile-anchor"); } }); } @@ -9123,6 +9134,22 @@ function ensureAdminOverrideTargetReady(target) { return false; } +function focusUserGovernanceWorkspace(tab = "effective", anchorId = "strategy-governance-anchor") { + appState.strategyDetailTab = tab; + setScreen("strategy"); + setTimeout(() => { + document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); + }, 0); +} + +function focusAdminGovernanceAuditWorkspace(anchorId = "admin-override-audit-anchor") { + appState.adminWorkbenchTab = "governance_audit"; + setScreen("admin-workbench"); + setTimeout(() => { + document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); + }, 0); +} + function openUserGlobalPolicyAction() { const project = requireSelectedProject(); const bundle = appState.userGlobalPolicy || {}; @@ -9153,6 +9180,7 @@ function openUserGlobalPolicyAction() { await loadAgentControlSurfaces(project.id); rememberAction("我的全局策略已保存", `已发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); + focusUserGovernanceWorkspace("global", "strategy-governance-anchor"); } }); } @@ -9188,6 +9216,7 @@ function openUserPlatformPolicyAction(platform) { await loadAgentControlSurfaces(project.id); rememberAction(`${platformLabel(normalizedPlatform)} 平台策略已保存`, `已发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); + focusUserGovernanceWorkspace("platform", "strategy-governance-anchor"); } }); } @@ -9223,6 +9252,7 @@ async function openUserGlobalPolicyHistoryAction(preferredVersionId = "") { await loadAgentControlSurfaces(project.id); rememberAction("我的全局策略已回滚", `已生成回滚版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); + focusUserGovernanceWorkspace("global", "strategy-governance-anchor"); } }); } @@ -9259,6 +9289,7 @@ async function openUserPlatformPolicyHistoryAction(platform, preferredVersionId await loadAgentControlSurfaces(project.id); rememberAction(`${platformLabel(normalizedPlatform)} 平台策略已回滚`, `已生成回滚版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); + focusUserGovernanceWorkspace("platform", "strategy-governance-anchor"); } }); } @@ -9431,6 +9462,7 @@ function openAdminOverridePolicyAction() { await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction("管理员覆盖策略已保存", `已为 ${formatAdminGovernanceTargetLabel(target)} 发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); + focusAdminGovernanceAuditWorkspace("admin-override-audit-anchor"); } }); } @@ -9468,6 +9500,7 @@ async function openAdminOverrideHistoryAction(preferredVersionId = "") { await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction("管理员覆盖已回滚", `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); + focusAdminGovernanceAuditWorkspace("admin-override-audit-anchor"); } }); } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index c757a61..c9fee39 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -645,17 +645,25 @@ test("system governance saves refresh control surfaces after persisting", () => }); test("oneliner profile history exposes rollback and audit entrypoints", () => { + const playbookFocusHelper = extractBetween(APP, "function focusPlaybookOneLinerWorkspace(anchorId = \"oneliner-profile-anchor\") {", "function openOneLinerProfileAction() {"); + const profile = extractBetween(APP, "function openOneLinerProfileAction()", "function resolvePreferredVersionId(history, preferredVersionId = \"\")"); const profileHistory = extractBetween(APP, "async function openOneLinerProfileHistoryAction(preferredVersionId = \"\")", "function parsePolicyJsonField(rawValue, label = \"策略 JSON\")"); const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()"); const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); + assert.match(playbookFocusHelper, /appState\.playbookDetailTab = "workspace"/); + assert.match(playbookFocusHelper, /setScreen\("playbook"\)/); + assert.match(playbookFocusHelper, /scrollIntoView/); + assert.match(profile, /focusPlaybookOneLinerWorkspace\("oneliner-profile-anchor"\)/); assert.match(APP, /function resolvePreferredVersionId\(history, preferredVersionId = ""\)/); assert.match(profileHistory, /\/v2\/oneliner\/profile\/versions/); assert.match(profileHistory, /\/v2\/oneliner\/profile\/audits/); assert.match(profileHistory, /\/v2\/oneliner\/profile\/rollback/); assert.match(profileHistory, /resolvePreferredVersionId\(history, preferredVersionId\)/); assert.match(profileHistory, /hideSubmit:\s*!selectedVersionId/); + assert.match(profileHistory, /focusPlaybookOneLinerWorkspace\("oneliner-profile-anchor"\)/); assert.match(playbook, /open-oneliner-profile-history/); + assert.match(playbook, /id="oneliner-profile-anchor"/); assert.match(actions, /name === "open-oneliner-profile-history"/); assert.match(actions, /openOneLinerProfileHistoryAction\(action\.dataset\.versionId \|\| ""\)/); }); @@ -717,11 +725,19 @@ test("admin governance actions apply local permission and empty-directory guards }); test("user governance UI exposes personal history and rollback entrypoints", () => { + const helpers = extractBetween(APP, "function ensureAdminGovernanceAccess()", "function openSystemMainPolicyAction() {"); const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()"); const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()"); const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()"); const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); + const userGlobal = extractBetween(APP, "function openUserGlobalPolicyAction()", "function openUserPlatformPolicyAction(platform) {"); + const userPlatform = extractBetween(APP, "function openUserPlatformPolicyAction(platform)", "async function openUserGlobalPolicyHistoryAction(preferredVersionId = \"\")"); + const userGlobalHistory = extractBetween(APP, "async function openUserGlobalPolicyHistoryAction(preferredVersionId = \"\")", "async function openUserPlatformPolicyHistoryAction(platform, preferredVersionId = \"\")"); + const userPlatformHistory = extractBetween(APP, "async function openUserPlatformPolicyHistoryAction(platform, preferredVersionId = \"\")", "function focusAdminGovernanceAgentsWorkspace(anchorId = \"admin-governance-anchor\") {"); + assert.match(helpers, /function focusUserGovernanceWorkspace\(tab = "effective", anchorId = "strategy-governance-anchor"\)/); + assert.match(helpers, /appState\.strategyDetailTab = tab/); + assert.match(helpers, /setScreen\("strategy"\)/); assert.match(playbook, /open-user-global-policy-history/); assert.match(playbook, /open-user-platform-policy-history/); assert.match(playbook, /handoff-to-main-agent/); @@ -730,8 +746,13 @@ test("user governance UI exposes personal history and rollback entrypoints", () assert.match(strategy, /管理员覆盖生效中/); assert.match(strategy, /handoff-to-main-agent/); assert.match(strategy, /strategy-main-agent-handoff/); + assert.match(strategy, /id="strategy-governance-anchor"/); assert.match(automation, /handoff-to-main-agent/); assert.match(automation, /automation-main-agent-handoff/); + assert.match(userGlobal, /focusUserGovernanceWorkspace\("global", "strategy-governance-anchor"\)/); + assert.match(userPlatform, /focusUserGovernanceWorkspace\("platform", "strategy-governance-anchor"\)/); + assert.match(userGlobalHistory, /focusUserGovernanceWorkspace\("global", "strategy-governance-anchor"\)/); + assert.match(userPlatformHistory, /focusUserGovernanceWorkspace\("platform", "strategy-governance-anchor"\)/); assert.match(actions, /name === "open-user-global-policy-history"/); assert.match(actions, /name === "open-user-platform-policy-history"/); @@ -744,8 +765,11 @@ test("admin override actions guard against missing governance targets", () => { assert.match(helpers, /function ensureAdminOverrideTargetReady\(target\)/); assert.match(helpers, /当前治理目录里还没有可选用户/); + assert.match(helpers, /function focusAdminGovernanceAuditWorkspace\(anchorId = "admin-override-audit-anchor"\)/); assert.match(override, /if \(!ensureAdminOverrideTargetReady\(target\)\) return;/); assert.match(overrideHistory, /if \(!ensureAdminOverrideTargetReady\(target\)\) return;/); + assert.match(override, /focusAdminGovernanceAuditWorkspace\("admin-override-audit-anchor"\)/); + assert.match(overrideHistory, /focusAdminGovernanceAuditWorkspace\("admin-override-audit-anchor"\)/); }); test("main agent result rendering offers a direct route back into the recommended screen", () => {