feat: finish mobile-native workbench flows
This commit is contained in:
@@ -866,6 +866,7 @@ function ensureActionUi() {
|
||||
modal.className = "action-modal-backdrop hidden";
|
||||
modal.innerHTML = `
|
||||
<div class="action-modal">
|
||||
<div class="sheet-handle" aria-hidden="true"></div>
|
||||
<div class="auth-head">
|
||||
<div>
|
||||
<h3 data-role="action-title">快速操作</h3>
|
||||
@@ -992,6 +993,7 @@ function ensureOneLinerUi() {
|
||||
panel.className = "oneliner-backdrop hidden";
|
||||
panel.innerHTML = `
|
||||
<div class="oneliner-panel">
|
||||
<div class="sheet-handle" aria-hidden="true"></div>
|
||||
<div class="oneliner-head">
|
||||
<div>
|
||||
<h3>OneLiner</h3>
|
||||
@@ -5150,13 +5152,35 @@ function renderProjectsScreen() {
|
||||
`${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: intakeHandoffAttrs })}`,
|
||||
`
|
||||
${renderMainAgentLandingNotice("intake")}
|
||||
<div class="hero-card">
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<h3>当前项目</h3>
|
||||
<p>${escapeHtml(selectedProject?.name || "还没有项目")} · ${escapeHtml(selectedProject?.description || "创建后即可承接对标、Agent 和生产任务。")}</p>
|
||||
<div class="chip-row" style="margin-top:14px;">
|
||||
${projects.map((project) => `<span class="chip ${project.id === appState.selectedProjectId ? "active" : ""}" data-action="select-project" data-project-id="${escapeHtml(project.id)}">${escapeHtml(project.name)}</span>`).join("") || `<span class="chip active">暂无项目</span>`}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前项目任务</strong>
|
||||
<span class="tag blue">${escapeHtml(selectedProject?.name || "未选项目")}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
selectedProject
|
||||
? `先围绕 ${selectedProject.name} 决定是继续导入内容、切换项目,还是直接交给主 Agent 推进。`
|
||||
: "先创建或切换到一个项目,再继续绑定账号和推进生产。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${actionTag(selectedProject ? "导入作品" : "新建项目", selectedProject ? "open-import-video-link" : "create-project")}
|
||||
${actionTag("切换项目", "open-dashboard-project-switcher")}
|
||||
${actionTag("交给主 Agent", "handoff-to-main-agent", intakeHandoffAttrs)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-top:14px; margin-bottom:14px;">
|
||||
<span class="tag blue">当前项目 ${escapeHtml(selectedProject?.name || "未选项目")}</span>
|
||||
<span class="tag">项目 ${escapeHtml(formatNumber(projects.length))}</span>
|
||||
<span class="tag green">内容源 ${escapeHtml(formatNumber(appState.contentSources.length))}</span>
|
||||
<span class="tag">${escapeHtml(formatNumber(safeArray(appState.dashboard.recent_jobs).length))} 个任务</span>
|
||||
</div>
|
||||
<div class="layout-grid grid-main" style="margin-top:18px;">
|
||||
<div class="side-stack">
|
||||
<div class="panel pad">
|
||||
@@ -5598,7 +5622,7 @@ function renderTrackingScreen() {
|
||||
`${button("同步全部", "refresh-tracking")} ${button("标记已读", "mark-tracking-read")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: trackingHandoffAttrs })} ${button("跳到找对标", "goto-discovery", "primary")}`,
|
||||
`
|
||||
${renderMainAgentLandingNotice("tracking")}
|
||||
<div class="hero-card">
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<h3>日报逻辑</h3>
|
||||
<p>按上次打开后汇总。上次打开距今 ${escapeHtml(daysSince(platformCursor))} 天,本次优先展示有更新且值得借鉴的内容。</p>
|
||||
<div class="chip-row" style="margin-top:14px;">
|
||||
@@ -5606,15 +5630,34 @@ function renderTrackingScreen() {
|
||||
<span class="chip">上次已读 ${escapeHtml(cursorLabel)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前跟踪任务</strong>
|
||||
<span class="tag blue">${escapeHtml(getPlatformShortLabel(currentPlatform))}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
digestItems.length
|
||||
? `先看最近 ${Math.min(digestItems.length, 12)} 条更新日报,再决定同步重点账号还是回到找对标。`
|
||||
: trackedAccounts.length
|
||||
? "先同步重点跟踪账号,等新作品出现后再回来看日报。"
|
||||
: "先去找对标把值得持续观察的账号加入跟踪。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${actionTag(digestItems.length ? "标记已读" : "同步全部", digestItems.length ? "mark-tracking-read" : "refresh-tracking")}
|
||||
${actionTag("跳到找对标", "goto-discovery")}
|
||||
${actionTag("交给主 Agent", "handoff-to-main-agent", trackingHandoffAttrs)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-top:14px; margin-bottom:14px;">
|
||||
<span class="tag blue">跟踪 ${escapeHtml(formatNumber(trackedAccounts.length))}</span>
|
||||
<span class="tag green">日报 ${escapeHtml(formatNumber(digestItems.length))}</span>
|
||||
<span class="tag">${escapeHtml(daysSince(platformCursor))} 天窗口</span>
|
||||
<span class="tag">${escapeHtml(getPlatformShortLabel(currentPlatform))}</span>
|
||||
</div>
|
||||
<div class="layout-grid grid-main" style="margin-top:18px;">
|
||||
<div class="side-stack">
|
||||
<div class="panel pad">
|
||||
<div class="panel-head"><div><h3>跟踪列表</h3><div class="panel-subtitle">真实跟踪对象与绑定 Agent</div></div><span class="tag">${escapeHtml(formatNumber(trackedAccounts.length))} 个</span></div>
|
||||
<div class="mobile-only compact-summary-row">
|
||||
<span class="tag blue">跟踪 ${escapeHtml(formatNumber(trackedAccounts.length))}</span>
|
||||
<span class="tag green">日报 ${escapeHtml(formatNumber(digestItems.length))}</span>
|
||||
<span class="tag">${escapeHtml(daysSince(platformCursor))} 天窗口</span>
|
||||
</div>
|
||||
<div class="list">
|
||||
${trackedAccounts.map((item) => `
|
||||
<div class="task-item compact">
|
||||
@@ -5687,7 +5730,7 @@ function renderAutomationScreen() {
|
||||
`${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: automationHandoffAttrs })} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`,
|
||||
`
|
||||
${renderMainAgentLandingNotice("automation")}
|
||||
<div class="hero-card">
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<h3>自动流程</h3>
|
||||
<p>当前按真实任务量和依赖健康状态给出看板,自动流程受阻时会直接在这里拦住动作。</p>
|
||||
<div class="mini-grid">
|
||||
@@ -5705,6 +5748,29 @@ function renderAutomationScreen() {
|
||||
</div>
|
||||
<span class="tag ${escapeHtml(overview.tone)}">${escapeHtml(overview.headline)}</span>
|
||||
</div>
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前自动流程任务</strong>
|
||||
<span class="tag blue">${escapeHtml(tabs.find((tab) => tab.value === activeTab)?.label || "依赖健康")}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
activeTab === "guards"
|
||||
? "先确认 AI 视频、实拍剪辑这类动作是否被拦截,再决定回到生产还是交给主 Agent。"
|
||||
: "先看当前依赖健康,再决定哪些动作可以继续自动推进。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${activeTab === "guards"
|
||||
? `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${actionTag("交给主 Agent", "handoff-to-main-agent", automationHandoffAttrs)}`
|
||||
: `${actionTag("刷新", "refresh-data")} ${actionTag("动作防呆", "select-page-tab", `data-page-tab-key="automationDetailTab" data-page-tab-value="guards"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", automationHandoffAttrs)}`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-bottom:14px;">
|
||||
<span class="tag blue">分析 ${escapeHtml(formatNumber(analysisJobs))}</span>
|
||||
<span class="tag green">AI 视频 ${escapeHtml(formatNumber(aiVideoJobs))}</span>
|
||||
<span class="tag">${escapeHtml(formatNumber(realCutJobs))} 实拍</span>
|
||||
<span class="tag ${escapeHtml(overview.tone)}">${escapeHtml(overview.headline)}</span>
|
||||
</div>
|
||||
${renderDetailTabs("automationDetailTab", tabs)}
|
||||
${activeTab === "health" ? renderIntegrationOverviewPanel({ showActions: false }) : `
|
||||
<div class="panel pad automation-guard-panel" style="box-shadow:none;">
|
||||
@@ -5750,7 +5816,7 @@ function renderOwnedScreen() {
|
||||
"这里先用当前登录账号和最近产出组合成第一版总览。",
|
||||
`${button("刷新", "refresh-data")} ${button("去 Agent", "goto-playbook", "primary")}`,
|
||||
`
|
||||
<div class="hero-card">
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<div class="entity-cell">
|
||||
<div class="avatar-lg">${escapeHtml(initials(me.display_name || me.username))}</div>
|
||||
<div>
|
||||
@@ -5765,6 +5831,28 @@ function renderOwnedScreen() {
|
||||
<div class="mini-card"><small>素材</small><strong>${escapeHtml(formatNumber(appState.documents.length))}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前账号任务</strong>
|
||||
<span class="tag blue">${escapeHtml(me.display_name || me.username || "当前账号")}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
activeJobs
|
||||
? "先处理当前待推进任务,再决定是否继续补 Agent 或整理项目信息。"
|
||||
: "当前任务压力不高,更适合补 Agent 策略、整理项目说明或继续跟踪。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${actionTag(activeJobs ? "去生产中心" : "去 Agent", activeJobs ? "goto-production" : "goto-playbook")}
|
||||
${actionTag("去我的项目", "goto-intake")}
|
||||
${actionTag("看跟踪账号", "goto-tracking")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-top:14px; margin-bottom:14px;">
|
||||
<span class="tag blue">当前项目 ${escapeHtml(selectedProject?.name || "未选项目")}</span>
|
||||
<span class="tag">${escapeHtml(getSelectedAssistant()?.name || "未选 Agent")}</span>
|
||||
<span class="tag ${activeJobs ? "orange" : "green"}">待推进 ${escapeHtml(formatNumber(activeJobs))}</span>
|
||||
<span class="tag green">已完成 ${escapeHtml(formatNumber(completedJobs))}</span>
|
||||
</div>
|
||||
<div class="layout-grid grid-main" style="margin-top:18px;">
|
||||
<div class="side-stack">
|
||||
<div class="panel pad">
|
||||
@@ -6431,6 +6519,40 @@ function renderReviewScreen() {
|
||||
`${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`,
|
||||
`
|
||||
${renderMainAgentLandingNotice("review")}
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<h3>复盘工作区</h3>
|
||||
<p>${escapeHtml(project?.name || "当前项目")} · 先把完成任务沉淀成可追溯复盘,再决定是否回到生产继续推进。</p>
|
||||
<div class="mini-grid">
|
||||
<div class="mini-card"><small>已保存</small><strong>${escapeHtml(formatNumber(reviews.length))}</strong></div>
|
||||
<div class="mini-card"><small>最近完成</small><strong>${escapeHtml(formatNumber(completed.length))}</strong></div>
|
||||
<div class="mini-card"><small>当前项目</small><strong>${escapeHtml(project?.name || "未选项目")}</strong></div>
|
||||
<div class="mini-card"><small>下一步</small><strong>${escapeHtml(completed.length ? "写复盘" : "回生产")}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前复盘任务</strong>
|
||||
<span class="tag blue">${escapeHtml(project?.name || "当前项目")}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
completed.length
|
||||
? "先把最近完成任务写成复盘,再决定是否继续沉淀发布结论。"
|
||||
: reviews.length
|
||||
? "先回看已保存复盘,再决定是否回到生产继续推进。"
|
||||
: "当前还没有可用复盘,先回到生产中心跑出一条完成链路。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${actionTag(completed.length ? "写复盘" : "去生产", completed.length ? "open-review-from-job" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")}
|
||||
${actionTag("刷新", "refresh-data")}
|
||||
${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-top:14px; margin-bottom:14px;">
|
||||
<span class="tag blue">已保存 ${escapeHtml(formatNumber(reviews.length))}</span>
|
||||
<span class="tag green">最近完成 ${escapeHtml(formatNumber(completed.length))}</span>
|
||||
<span class="tag">${escapeHtml(project?.name || "未选项目")}</span>
|
||||
<span class="tag">${escapeHtml(completed.length ? "可继续写复盘" : "先回生产")}</span>
|
||||
</div>
|
||||
<div class="layout-grid grid-main">
|
||||
<div class="side-stack">
|
||||
<div class="panel pad">
|
||||
|
||||
@@ -533,6 +533,14 @@ select {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.sheet-handle {
|
||||
width: 48px;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: rgba(133, 155, 189, 0.34);
|
||||
margin: 0 auto 6px;
|
||||
}
|
||||
|
||||
.auth-head {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
@@ -2068,17 +2076,21 @@ tbody tr:hover {
|
||||
.action-modal {
|
||||
width: 100%;
|
||||
max-height: min(90vh, 100%);
|
||||
border-radius: 20px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.oneliner-panel {
|
||||
width: 100%;
|
||||
height: min(88vh, 100%);
|
||||
border-radius: 22px;
|
||||
padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.auth-modal,
|
||||
.action-modal,
|
||||
.oneliner-panel {
|
||||
border-radius: 24px 24px 0 0;
|
||||
}
|
||||
|
||||
.oneliner-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -2100,6 +2112,12 @@ tbody tr:hover {
|
||||
|
||||
.auth-actions {
|
||||
flex-direction: column-reverse;
|
||||
position: sticky;
|
||||
bottom: calc(env(safe-area-inset-bottom) * -1);
|
||||
margin: 8px -18px -18px;
|
||||
padding: 12px 18px calc(18px + env(safe-area-inset-bottom));
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.78) 0%, rgba(255, 255, 255, 0.98) 28%);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.auth-actions .btn {
|
||||
@@ -2107,6 +2125,15 @@ tbody tr:hover {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.oneliner-composer {
|
||||
position: sticky;
|
||||
bottom: calc(env(safe-area-inset-bottom) * -1);
|
||||
margin: 0 -18px -18px;
|
||||
padding: 12px 18px calc(18px + env(safe-area-inset-bottom));
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.76) 0%, rgba(255, 255, 255, 0.985) 26%);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.screen {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,14 @@ test("mobile shell removes duplicated desktop topbar and collapses the main agen
|
||||
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab-text\s*\{[\s\S]*display:\s*none/);
|
||||
});
|
||||
|
||||
test("mobile action sheets and oneliner runtime behave like bottom sheets", () => {
|
||||
assert.match(APP, /class="sheet-handle"/);
|
||||
assert.match(CSS, /\.sheet-handle\s*\{/);
|
||||
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-modal,\s*[\s\S]*\.action-modal,\s*[\s\S]*\.oneliner-panel\s*\{[\s\S]*border-radius:\s*24px 24px 0 0/);
|
||||
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-actions\s*\{[\s\S]*position:\s*sticky/);
|
||||
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-composer\s*\{[\s\S]*position:\s*sticky/);
|
||||
});
|
||||
|
||||
test("mobile touch targets raise tappable buttons, tabs, and action tags closer to native sizes", () => {
|
||||
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.btn,\s*[\s\S]*\.tab,\s*[\s\S]*\.tag\.clickable-tag\s*\{[\s\S]*min-height:\s*44px/);
|
||||
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.btn,\s*[\s\S]*\.tab,\s*[\s\S]*\.tag\.clickable-tag\s*\{[\s\S]*display:\s*inline-flex/);
|
||||
@@ -218,13 +226,23 @@ test("discovery and production screens expose mobile focus cards with next-step
|
||||
|
||||
test("mobile heavy screens mark redundant desktop metric blocks for compact hiding", () => {
|
||||
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
||||
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
|
||||
const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
||||
const owned = extractBetween(APP, "function renderOwnedScreen()", "function renderPlaybookScreen()");
|
||||
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
||||
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
||||
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
||||
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
|
||||
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
|
||||
const cssMobile = extractBetween(CSS, "@media (max-width: 760px) {", "@media (max-width: 560px) {");
|
||||
|
||||
assert.match(discovery, /discovery-selected-hero mobile-secondary-card/);
|
||||
assert.match(tracking, /hero-card mobile-secondary-card/);
|
||||
assert.match(automation, /hero-card mobile-secondary-card/);
|
||||
assert.match(owned, /hero-card mobile-secondary-card/);
|
||||
assert.match(projects, /hero-card mobile-secondary-card/);
|
||||
assert.match(production, /production-queue-grid/);
|
||||
assert.match(review, /hero-card mobile-secondary-card/);
|
||||
assert.match(playbook, /hero-card mobile-secondary-card/);
|
||||
assert.match(strategy, /hero-card mobile-secondary-card/);
|
||||
assert.match(cssMobile, /\.discovery-selected-hero \.mini-grid/);
|
||||
@@ -233,6 +251,39 @@ test("mobile heavy screens mark redundant desktop metric blocks for compact hidi
|
||||
assert.match(cssMobile, /\.mobile-secondary-card/);
|
||||
});
|
||||
|
||||
test("remaining mobile workbench screens expose focus cards and compact summaries", () => {
|
||||
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
||||
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
|
||||
const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
||||
const owned = extractBetween(APP, "function renderOwnedScreen()", "function renderPlaybookScreen()");
|
||||
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
||||
|
||||
assert.match(projects, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(projects, /当前项目任务/);
|
||||
assert.match(projects, /mobile-only compact-summary-row/);
|
||||
assert.match(projects, /当前项目/);
|
||||
|
||||
assert.match(tracking, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(tracking, /当前跟踪任务/);
|
||||
assert.match(tracking, /mobile-only compact-summary-row/);
|
||||
assert.match(tracking, /日报/);
|
||||
|
||||
assert.match(automation, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(automation, /当前自动流程任务/);
|
||||
assert.match(automation, /mobile-only compact-summary-row/);
|
||||
assert.match(automation, /AI 视频/);
|
||||
|
||||
assert.match(owned, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(owned, /当前账号任务/);
|
||||
assert.match(owned, /mobile-only compact-summary-row/);
|
||||
assert.match(owned, /当前项目/);
|
||||
|
||||
assert.match(review, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(review, /当前复盘任务/);
|
||||
assert.match(review, /mobile-only compact-summary-row/);
|
||||
assert.match(review, /已保存/);
|
||||
});
|
||||
|
||||
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/);
|
||||
|
||||
Reference in New Issue
Block a user