diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js
index ff666be..0eed9a4 100644
--- a/web/storyforge-web-v4/assets/app.js
+++ b/web/storyforge-web-v4/assets/app.js
@@ -5794,6 +5794,12 @@ function renderPlaybookScreen() {
先处理你当前真的会用到的 Agent 信息,系统治理内容已移到管理员配置台。
+
+ 主 Agent ${escapeHtml(appState.onelinerProfile?.display_name || "OneLiner")}
+ ${escapeHtml(currentAssistant ? `当前 ${currentAssistant.name}` : "当前未选 Agent")}
+ 模型 ${escapeHtml(formatNumber(models.length))}
+ ${escapeHtml(activeAdminOverrideNotice?.title ? "有管理员覆盖" : "无管理员覆盖")}
+
${renderDetailTabs("playbookDetailTab", tabs)}
${activeTab === "workspace" ? `
@@ -6268,6 +6274,12 @@ function renderStrategyScreen() {
先看当前生效,再回看你自己的历史和管理员覆盖,不必再通过多个弹窗来回切。
+
+ 当前生效 ${escapeHtml(formatNumber(safeArray(appState.onelinerGovernanceEffective?.layers).length))} 层
+ ${escapeHtml(platformLabel(platform))} 平台策略
+ 变更 ${escapeHtml(formatNumber(safeArray(appState.userPolicyAudits).length))}
+ ${escapeHtml(activeAdminOverrideNotice?.title ? "管理员覆盖生效" : "无管理员覆盖")}
+
${renderDetailTabs("strategyDetailTab", tabs)}
${activeTab === "effective" ? `
diff --git a/web/storyforge-web-v4/assets/styles.css b/web/storyforge-web-v4/assets/styles.css
index b0e9545..d0fca0d 100644
--- a/web/storyforge-web-v4/assets/styles.css
+++ b/web/storyforge-web-v4/assets/styles.css
@@ -2040,6 +2040,13 @@ tbody tr:hover {
overflow: hidden;
}
+ .panel-subtitle {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+
.action-row {
width: 100%;
flex-wrap: nowrap;
@@ -2186,6 +2193,17 @@ tbody tr:hover {
align-items: flex-start;
}
+ .task-item p,
+ .entity-card p,
+ .topic-card p,
+ .queue-card p,
+ .review-card p {
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+
.avatar-lg {
width: 40px;
height: 40px;
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
index 6086e5f..bc24df7 100644
--- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs
+++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
@@ -81,6 +81,13 @@ test("strategy navigation and screen are real routes", () => {
assert.match(APP, /screenMap\.strategy\.innerHTML = renderStrategyScreen\(\);/);
});
+test("strategy screen exposes a compact mobile summary for current governance state", () => {
+ const source = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
+ assert.match(source, /mobile-only compact-summary-row/);
+ assert.match(source, /当前生效/);
+ assert.match(source, /平台策略/);
+});
+
test("automation screen stays user-facing and excludes admin-only panels", () => {
const source = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
assert.doesNotMatch(source, /renderTenantQuotaPanel\(/);
@@ -94,6 +101,7 @@ test("agent screen excludes quota and registry panels and uses page tabs", () =>
assert.doesNotMatch(source, /renderTenantQuotaPanel\(/);
assert.doesNotMatch(source, /renderOneLinerActionRegistryPanel\(/);
assert.match(source, /renderDetailTabs\("playbookDetailTab"/);
+ assert.match(source, /mobile-only compact-summary-row/);
assert.match(source, /renderGovernanceSummaryCard\(/);
assert.match(source, /open-user-global-policy/);
assert.match(source, /open-user-platform-policy/);
@@ -122,6 +130,11 @@ test("projects screen uses an adaptive project grid instead of a fixed three-col
assert.match(CSS, /\.entity-card\.pad\s*\{[\s\S]*padding:\s*15px/);
});
+test("mobile typography clamps subtitles and dense card copy on small screens", () => {
+ assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.panel-subtitle\s*\{[\s\S]*-webkit-line-clamp:\s*2/);
+ assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.task-item p,[\s\S]*\.review-card p\s*\{[\s\S]*-webkit-line-clamp:\s*3/);
+});
+
test("dashboard and project screens distinguish auto-connecting from truly disconnected", () => {
const dashboard = extractBetween(APP, "function renderDashboardScreen()", "function renderProjectsScreen()");
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");