diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 4d584c6..e183b8c 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -778,6 +778,7 @@ function ensureAuthUi() {
+
@@ -792,11 +793,13 @@ function renderAuthUi() { ensureAuthUi(); const session = appState.session; const openButton = document.querySelector('[data-action="open-auth"]'); - const logoutButton = document.querySelector('[data-action="logout-session"]'); + const logoutButtons = document.querySelectorAll('[data-action="logout-session"]'); const status = document.querySelector(".auth-status"); const message = document.querySelector('[data-role="auth-message"]'); if (openButton) openButton.textContent = session ? "连接状态" : "自动连接"; - if (logoutButton) logoutButton.hidden = !session; + logoutButtons.forEach((button) => { + button.hidden = !session; + }); if (status) { status.textContent = appState.busy ? appState.message || "正在加载..." @@ -5348,7 +5351,7 @@ function renderDiscoveryScreen() { ]; const activeTab = getActiveDetailTab("discoveryDetailTab", detailTabs); const selectedSummaryHtml = ` -
+
${escapeHtml(initials(getAccountName(selected) || "SF"))}
@@ -6035,7 +6038,7 @@ function renderProductionScreen() { ${renderMainAgentLandingNotice("production")}

生产队列

最近任务的真实状态
-
+

分析任务

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))} 条

实拍剪辑

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))} 条

AI 视频

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))} 条

diff --git a/web/storyforge-web-v4/assets/styles.css b/web/storyforge-web-v4/assets/styles.css index 5f65c16..1805d6f 100644 --- a/web/storyforge-web-v4/assets/styles.css +++ b/web/storyforge-web-v4/assets/styles.css @@ -1930,6 +1930,7 @@ tbody tr:hover { } .topbar { + display: none; margin-top: 6px; padding: 14px; border-radius: 18px; @@ -2193,6 +2194,11 @@ tbody tr:hover { text-align: center; } + .discovery-selected-hero .mini-grid, + .production-queue-grid { + display: none; + } + .mobile-flow-focus-card { display: grid; gap: 10px; @@ -2263,7 +2269,16 @@ tbody tr:hover { .oneliner-fab { right: 14px; bottom: calc(96px + env(safe-area-inset-bottom)); - padding: 11px 12px; + width: 52px; + height: 52px; + padding: 0; + gap: 0; + justify-content: center; + border-radius: 18px; + } + + .oneliner-fab-text { + display: none; } table { diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 9b8f3dc..bfd70eb 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -30,6 +30,7 @@ test("mobile shell includes a native-like header, drawer toggle, and bottom tab assert.match(HTML, /data-action="open-mobile-sidebar"/); assert.match(HTML, /class="mobile-tabbar"/); assert.match(HTML, /class="mobile-sidebar-backdrop"/); + assert.match(HTML, /class="mobile-workspace-summary"/); assert.match(HTML, /data-screen-target="dashboard"[\s\S]*mobile-tabbar-item/); assert.match(HTML, /data-screen-target="intake"[\s\S]*mobile-tabbar-item/); assert.match(HTML, /data-screen-target="discovery"[\s\S]*mobile-tabbar-item/); @@ -45,6 +46,10 @@ test("mobile shell styling uses safe-area padding, drawer navigation, and fixed assert.match(CSS, /\.mobile-sidebar-open\s+\.sidebar\s*\{[\s\S]*transform:\s*translateX\(0\)/); assert.match(CSS, /\.content\s*\{[\s\S]*padding-bottom:\s*calc\(110px \+ env\(safe-area-inset-bottom\)\)/); assert.match(CSS, /\.oneliner-fab\s*\{[\s\S]*bottom:\s*calc\(96px \+ env\(safe-area-inset-bottom\)\)/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.mobile-workspace-summary\s*\{[\s\S]*display:\s*flex/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.workspace-switch span\s*\{[\s\S]*display:\s*none/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-status\s*\{[\s\S]*display:\s*none/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab-text\s*\{[\s\S]*display:\s*none/); }); test("mobile shell javascript syncs drawer state and active labels with the current screen", () => { @@ -54,6 +59,7 @@ test("mobile shell javascript syncs drawer state and active labels with the curr assert.match(APP, /function setMobileSidebarOpen\(next\)/); assert.match(APP, /function getScreenLabel\(screenId = appState\.screen\)/); assert.match(APP, /function syncMobileShell\(\)/); + assert.match(APP, /const mobileWorkspaceSummary = document\.querySelector\("\.mobile-workspace-summary"\)/); assert.match(shell, /syncMobileShell\(\);/); assert.match(APP, /setMobileSidebarOpen\(false\);[\s\S]*appState\.screen = resolvedId;/); assert.match(clicks, /name === "open-mobile-sidebar"/); @@ -69,6 +75,13 @@ test("mobile layout turns screen actions and page tabs into native-like horizont assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.page-detail-tabs \.tab\s*\{[\s\S]*flex:\s*0 0 auto/); }); +test("mobile shell removes duplicated desktop topbar and collapses the main agent fab", () => { + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.topbar\s*\{[\s\S]*display:\s*none/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab\s*\{[\s\S]*width:\s*52px/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab\s*\{[\s\S]*justify-content:\s*center/); + assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab-text\s*\{[\s\S]*display:\s*none/); +}); + test("detail tab buttons expose the active state for touch navigation", () => { const detailTabs = extractBetween(APP, "function renderDetailTabs(stateKey, tabs) {", "function renderDiscoveryOverviewSection("); assert.match(detailTabs, /aria-pressed="\$\{tab\.value === active \? "true" : "false"\}"/); @@ -150,6 +163,17 @@ test("discovery and production screens expose mobile focus cards with next-step assert.match(cssMobile, /\.mobile-flow-focus-card/); }); +test("mobile heavy screens mark redundant desktop metric blocks for compact hiding", () => { + const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()"); + const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()"); + const cssMobile = extractBetween(CSS, "@media (max-width: 760px) {", "@media (max-width: 560px) {"); + + assert.match(discovery, /discovery-selected-hero/); + assert.match(production, /production-queue-grid/); + assert.match(cssMobile, /\.discovery-selected-hero \.mini-grid/); + assert.match(cssMobile, /\.production-queue-grid/); +}); + test("projects screen uses an adaptive project grid instead of a fixed three-column squeeze", () => { const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab("); assert.match(projects, /project-status-grid/);