feat: tighten mobile-first workbench chrome
This commit is contained in:
@@ -778,6 +778,7 @@ function ensureAuthUi() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="helper-text" data-role="auth-message"></div>
|
<div class="helper-text" data-role="auth-message"></div>
|
||||||
<div class="auth-actions">
|
<div class="auth-actions">
|
||||||
|
<button class="btn btn-secondary" type="button" data-action="logout-session">退出工作区</button>
|
||||||
<button class="btn btn-secondary" type="button" data-action="auth-refresh">重新加载</button>
|
<button class="btn btn-secondary" type="button" data-action="auth-refresh">重新加载</button>
|
||||||
<button class="btn btn-primary" type="submit" data-action="submit-auth">自动连接</button>
|
<button class="btn btn-primary" type="submit" data-action="submit-auth">自动连接</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -792,11 +793,13 @@ function renderAuthUi() {
|
|||||||
ensureAuthUi();
|
ensureAuthUi();
|
||||||
const session = appState.session;
|
const session = appState.session;
|
||||||
const openButton = document.querySelector('[data-action="open-auth"]');
|
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 status = document.querySelector(".auth-status");
|
||||||
const message = document.querySelector('[data-role="auth-message"]');
|
const message = document.querySelector('[data-role="auth-message"]');
|
||||||
if (openButton) openButton.textContent = session ? "连接状态" : "自动连接";
|
if (openButton) openButton.textContent = session ? "连接状态" : "自动连接";
|
||||||
if (logoutButton) logoutButton.hidden = !session;
|
logoutButtons.forEach((button) => {
|
||||||
|
button.hidden = !session;
|
||||||
|
});
|
||||||
if (status) {
|
if (status) {
|
||||||
status.textContent = appState.busy
|
status.textContent = appState.busy
|
||||||
? appState.message || "正在加载..."
|
? appState.message || "正在加载..."
|
||||||
@@ -5348,7 +5351,7 @@ function renderDiscoveryScreen() {
|
|||||||
];
|
];
|
||||||
const activeTab = getActiveDetailTab("discoveryDetailTab", detailTabs);
|
const activeTab = getActiveDetailTab("discoveryDetailTab", detailTabs);
|
||||||
const selectedSummaryHtml = `
|
const selectedSummaryHtml = `
|
||||||
<div class="hero-card" style="padding:18px;">
|
<div class="hero-card discovery-selected-hero" style="padding:18px;">
|
||||||
<div class="entity-cell">
|
<div class="entity-cell">
|
||||||
<div class="avatar-lg">${escapeHtml(initials(getAccountName(selected) || "SF"))}</div>
|
<div class="avatar-lg">${escapeHtml(initials(getAccountName(selected) || "SF"))}</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -6035,7 +6038,7 @@ function renderProductionScreen() {
|
|||||||
${renderMainAgentLandingNotice("production")}
|
${renderMainAgentLandingNotice("production")}
|
||||||
<div class="panel pad">
|
<div class="panel pad">
|
||||||
<div class="panel-head"><div><h3>生产队列</h3><div class="panel-subtitle">最近任务的真实状态</div></div></div>
|
<div class="panel-head"><div><h3>生产队列</h3><div class="panel-subtitle">最近任务的真实状态</div></div></div>
|
||||||
<div class="layout-grid grid-4" style="margin-top:16px;">
|
<div class="layout-grid grid-4 production-queue-grid" style="margin-top:16px;">
|
||||||
<div class="queue-card"><h4>分析任务</h4><p>最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))} 条</p></div>
|
<div class="queue-card"><h4>分析任务</h4><p>最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))} 条</p></div>
|
||||||
<div class="queue-card"><h4>实拍剪辑</h4><p>最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))} 条</p></div>
|
<div class="queue-card"><h4>实拍剪辑</h4><p>最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))} 条</p></div>
|
||||||
<div class="queue-card"><h4>AI 视频</h4><p>最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))} 条</p></div>
|
<div class="queue-card"><h4>AI 视频</h4><p>最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))} 条</p></div>
|
||||||
|
|||||||
@@ -1930,6 +1930,7 @@ tbody tr:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
|
display: none;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
@@ -2193,6 +2194,11 @@ tbody tr:hover {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.discovery-selected-hero .mini-grid,
|
||||||
|
.production-queue-grid {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-flow-focus-card {
|
.mobile-flow-focus-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -2263,7 +2269,16 @@ tbody tr:hover {
|
|||||||
.oneliner-fab {
|
.oneliner-fab {
|
||||||
right: 14px;
|
right: 14px;
|
||||||
bottom: calc(96px + env(safe-area-inset-bottom));
|
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 {
|
table {
|
||||||
|
|||||||
@@ -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, /data-action="open-mobile-sidebar"/);
|
||||||
assert.match(HTML, /class="mobile-tabbar"/);
|
assert.match(HTML, /class="mobile-tabbar"/);
|
||||||
assert.match(HTML, /class="mobile-sidebar-backdrop"/);
|
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="dashboard"[\s\S]*mobile-tabbar-item/);
|
||||||
assert.match(HTML, /data-screen-target="intake"[\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/);
|
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, /\.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, /\.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, /\.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", () => {
|
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 setMobileSidebarOpen\(next\)/);
|
||||||
assert.match(APP, /function getScreenLabel\(screenId = appState\.screen\)/);
|
assert.match(APP, /function getScreenLabel\(screenId = appState\.screen\)/);
|
||||||
assert.match(APP, /function syncMobileShell\(\)/);
|
assert.match(APP, /function syncMobileShell\(\)/);
|
||||||
|
assert.match(APP, /const mobileWorkspaceSummary = document\.querySelector\("\.mobile-workspace-summary"\)/);
|
||||||
assert.match(shell, /syncMobileShell\(\);/);
|
assert.match(shell, /syncMobileShell\(\);/);
|
||||||
assert.match(APP, /setMobileSidebarOpen\(false\);[\s\S]*appState\.screen = resolvedId;/);
|
assert.match(APP, /setMobileSidebarOpen\(false\);[\s\S]*appState\.screen = resolvedId;/);
|
||||||
assert.match(clicks, /name === "open-mobile-sidebar"/);
|
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/);
|
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", () => {
|
test("detail tab buttons expose the active state for touch navigation", () => {
|
||||||
const detailTabs = extractBetween(APP, "function renderDetailTabs(stateKey, tabs) {", "function renderDiscoveryOverviewSection(");
|
const detailTabs = extractBetween(APP, "function renderDetailTabs(stateKey, tabs) {", "function renderDiscoveryOverviewSection(");
|
||||||
assert.match(detailTabs, /aria-pressed="\$\{tab\.value === active \? "true" : "false"\}"/);
|
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/);
|
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", () => {
|
test("projects screen uses an adaptive project grid instead of a fixed three-column squeeze", () => {
|
||||||
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
||||||
assert.match(projects, /project-status-grid/);
|
assert.match(projects, /project-status-grid/);
|
||||||
|
|||||||
Reference in New Issue
Block a user