From 3bf597fed3bd1a58b4880e3c26d49ce0022d7c21 Mon Sep 17 00:00:00 2001
From: Haoxiang Cheng <2739441541@qq.com>
Date: Thu, 24 Sep 2026 02:13:28 +0800
Subject: [PATCH 1/2] fix: style the research back button and reuse the ATL
Community pill
The research workbench back button and the research shelf's empty-state
Community link rendered as bare unstyled controls. The back button now
matches the app's secondary button look; the Community link reuses the
existing agents-empty-community-btn pill and its delegated handler.
---
dashboard/backend/tests/test_admin_analytics_frontend.py | 4 ++--
dashboard/backend/tests/test_analytics_frontend.py | 2 +-
.../backend/tests/test_backtest_comparison_frontend.py | 4 ++--
dashboard/backend/tests/test_frontend_fast_boot.py | 4 ++--
dashboard/frontend/app.html | 4 ++--
dashboard/frontend/app.js | 6 ++----
dashboard/frontend/styles.css | 2 ++
7 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/dashboard/backend/tests/test_admin_analytics_frontend.py b/dashboard/backend/tests/test_admin_analytics_frontend.py
index 3054ac0b..b7303012 100644
--- a/dashboard/backend/tests/test_admin_analytics_frontend.py
+++ b/dashboard/backend/tests/test_admin_analytics_frontend.py
@@ -161,8 +161,8 @@ def test_profile_menu_admin_entry_opens_the_admin_page():
def test_app_lifecycle_and_cache_versions_are_wired():
# Lockstep owner for the console's bumped tags and the /admin page's pins:
# every bump edits this test in the same change (Global Constraints).
- assert 'styles.css?v=145' in APP_HTML
- assert 'app.js?v=138' in APP_HTML
+ assert 'styles.css?v=146' in APP_HTML
+ assert 'app.js?v=139' in APP_HTML
assert 'js/admin-tabs.js?v=12' in APP_HTML
for tag in (
'href="admin.css?v=4"',
diff --git a/dashboard/backend/tests/test_analytics_frontend.py b/dashboard/backend/tests/test_analytics_frontend.py
index c620362b..37fdaf75 100644
--- a/dashboard/backend/tests/test_analytics_frontend.py
+++ b/dashboard/backend/tests/test_analytics_frontend.py
@@ -41,7 +41,7 @@ def _function_body(source: str, signature: str) -> str:
def test_analytics_script_loads_between_app_and_page_scripts():
- app_at = APP_HTML.index('')
+ app_at = APP_HTML.index('')
analytics_at = APP_HTML.index(
''
)
diff --git a/dashboard/backend/tests/test_backtest_comparison_frontend.py b/dashboard/backend/tests/test_backtest_comparison_frontend.py
index fe0e9925..7f22c9c3 100644
--- a/dashboard/backend/tests/test_backtest_comparison_frontend.py
+++ b/dashboard/backend/tests/test_backtest_comparison_frontend.py
@@ -191,8 +191,8 @@ def test_exact_raw_ties_mark_every_tied_series_best():
def test_comparison_script_and_semantic_table_ship_before_app():
helper = ''
- app = ''
- assert 'href="styles.css?v=145"' in APP_HTML
+ app = ''
+ assert 'href="styles.css?v=146"' in APP_HTML
assert APP_HTML.index(helper) < APP_HTML.index(app)
for element_id in (
"performanceLegend",
diff --git a/dashboard/backend/tests/test_frontend_fast_boot.py b/dashboard/backend/tests/test_frontend_fast_boot.py
index 80bc3f1b..6f6b5f13 100644
--- a/dashboard/backend/tests/test_frontend_fast_boot.py
+++ b/dashboard/backend/tests/test_frontend_fast_boot.py
@@ -191,9 +191,9 @@ def test_cache_busters_bumped():
# the next bump, so the exact one looks like the broken guard and gets
# "fixed" by loosening it. That collision has already cost this repo one
# round of follow-ups (#347/#348).
- assert "app.js?v=138" in APP_HTML
+ assert "app.js?v=139" in APP_HTML
assert "js/agent-editor.js?v=31" in APP_HTML
- assert "styles.css?v=145" in APP_HTML
+ assert "styles.css?v=146" in APP_HTML
assert "js/leaderboard.js?v=33" in APP_HTML
assert "home-page.js?v=50" in APP_HTML
assert "js/credit-format.js?v=1" in APP_HTML
diff --git a/dashboard/frontend/app.html b/dashboard/frontend/app.html
index 245a4b67..c9b62f51 100644
--- a/dashboard/frontend/app.html
+++ b/dashboard/frontend/app.html
@@ -13,7 +13,7 @@
because every API call is a CORS request. -->
-
+
@@ -2393,7 +2393,7 @@
Refund Credits purchase
-
+
diff --git a/dashboard/frontend/app.js b/dashboard/frontend/app.js
index 0922ac1c..b5a591ef 100644
--- a/dashboard/frontend/app.js
+++ b/dashboard/frontend/app.js
@@ -13027,11 +13027,9 @@ async function renderResearchShelf() {
grid.innerHTML = '';
if (emptyEl) {
emptyEl.hidden = false;
- emptyEl.innerHTML = 'No research agents yet. Add one from .';
+ emptyEl.innerHTML = 'No research agents yet. Add one from '
+ + communityShelfButtonHtml('all') + '.';
}
- grid.querySelectorAll('[data-goto-community]').forEach((btn) => {
- btn.addEventListener('click', () => navigateToPage('community'));
- });
return;
}
if (emptyEl) emptyEl.hidden = true;
diff --git a/dashboard/frontend/styles.css b/dashboard/frontend/styles.css
index 984ab3d4..2066094f 100644
--- a/dashboard/frontend/styles.css
+++ b/dashboard/frontend/styles.css
@@ -13880,3 +13880,5 @@ table.sr-only {
@media(max-width:900px){.research-columns{grid-template-columns:1fr}}
.marketplace-card--research{border-color:rgba(103,232,249,.25)}
.research-fact-icon{width:14px;height:14px;vertical-align:-2px;margin-right:2px}
+.research-back-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;margin-bottom:16px;border-radius:8px;border:1px solid rgba(148,163,184,.28);background:transparent;color:var(--text-muted,#94a3b8);font:inherit;font-size:13px;cursor:pointer}
+.research-back-btn:hover{color:var(--accent,#67e8f9);border-color:rgba(103,232,249,.4)}
From 2ef38c2208a4944983c03950ef3f83bd249db612 Mon Sep 17 00:00:00 2001
From: Haoxiang Cheng <2739441541@qq.com>
Date: Thu, 24 Sep 2026 02:29:49 +0800
Subject: [PATCH 2/2] feat: research agent Remove action + always-bound
workbench Back
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- My Agents research cards gain a Remove action (confirm dialog, DELETE to
the research add endpoint, shelf refresh) — re-addable from Community at
any time.
- The workbench Back button is bound once at init instead of after a
successful manifest fetch: on the manifest-failure path the old binding
never ran, trapping the user on a dead workbench (observed on the Vercel
deploy while the agent service is unreachable).
- app.js cache version 139→140 with test pins.
---
.../tests/test_admin_analytics_frontend.py | 2 +-
.../backend/tests/test_analytics_frontend.py | 2 +-
.../test_backtest_comparison_frontend.py | 2 +-
.../backend/tests/test_frontend_fast_boot.py | 2 +-
dashboard/frontend/app.html | 2 +-
dashboard/frontend/app.js | 22 +++++++++++++++++--
dashboard/frontend/styles.css | 3 +++
7 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/dashboard/backend/tests/test_admin_analytics_frontend.py b/dashboard/backend/tests/test_admin_analytics_frontend.py
index b7303012..3830a206 100644
--- a/dashboard/backend/tests/test_admin_analytics_frontend.py
+++ b/dashboard/backend/tests/test_admin_analytics_frontend.py
@@ -162,7 +162,7 @@ def test_app_lifecycle_and_cache_versions_are_wired():
# Lockstep owner for the console's bumped tags and the /admin page's pins:
# every bump edits this test in the same change (Global Constraints).
assert 'styles.css?v=146' in APP_HTML
- assert 'app.js?v=139' in APP_HTML
+ assert 'app.js?v=140' in APP_HTML
assert 'js/admin-tabs.js?v=12' in APP_HTML
for tag in (
'href="admin.css?v=4"',
diff --git a/dashboard/backend/tests/test_analytics_frontend.py b/dashboard/backend/tests/test_analytics_frontend.py
index 37fdaf75..008a64d4 100644
--- a/dashboard/backend/tests/test_analytics_frontend.py
+++ b/dashboard/backend/tests/test_analytics_frontend.py
@@ -41,7 +41,7 @@ def _function_body(source: str, signature: str) -> str:
def test_analytics_script_loads_between_app_and_page_scripts():
- app_at = APP_HTML.index('')
+ app_at = APP_HTML.index('')
analytics_at = APP_HTML.index(
''
)
diff --git a/dashboard/backend/tests/test_backtest_comparison_frontend.py b/dashboard/backend/tests/test_backtest_comparison_frontend.py
index 7f22c9c3..b4104090 100644
--- a/dashboard/backend/tests/test_backtest_comparison_frontend.py
+++ b/dashboard/backend/tests/test_backtest_comparison_frontend.py
@@ -191,7 +191,7 @@ def test_exact_raw_ties_mark_every_tied_series_best():
def test_comparison_script_and_semantic_table_ship_before_app():
helper = ''
- app = ''
+ app = ''
assert 'href="styles.css?v=146"' in APP_HTML
assert APP_HTML.index(helper) < APP_HTML.index(app)
for element_id in (
diff --git a/dashboard/backend/tests/test_frontend_fast_boot.py b/dashboard/backend/tests/test_frontend_fast_boot.py
index 6f6b5f13..fa6547f1 100644
--- a/dashboard/backend/tests/test_frontend_fast_boot.py
+++ b/dashboard/backend/tests/test_frontend_fast_boot.py
@@ -191,7 +191,7 @@ def test_cache_busters_bumped():
# the next bump, so the exact one looks like the broken guard and gets
# "fixed" by loosening it. That collision has already cost this repo one
# round of follow-ups (#347/#348).
- assert "app.js?v=139" in APP_HTML
+ assert "app.js?v=140" in APP_HTML
assert "js/agent-editor.js?v=31" in APP_HTML
assert "styles.css?v=146" in APP_HTML
assert "js/leaderboard.js?v=33" in APP_HTML
diff --git a/dashboard/frontend/app.html b/dashboard/frontend/app.html
index c9b62f51..9f404f33 100644
--- a/dashboard/frontend/app.html
+++ b/dashboard/frontend/app.html
@@ -2393,7 +2393,7 @@
Refund Credits purchase
-
+
diff --git a/dashboard/frontend/app.js b/dashboard/frontend/app.js
index b5a591ef..db657001 100644
--- a/dashboard/frontend/app.js
+++ b/dashboard/frontend/app.js
@@ -12983,7 +12983,13 @@ let researchWorkbenchTemplateId = null;
let researchWorkbenchReturnView = null;
let researchPollTimer = null;
let researchSubmitInFlight = false;
+document.addEventListener('DOMContentLoaded', () => {
+ document.getElementById('researchBackBtn')?.addEventListener('click', hideResearchWorkbench);
+});
+// Back is bound once at init, not per-open: the manifest-failure path returns
+// before openResearchWorkbench would reach its binding, and a dead Back on a
+// failed-to-load workbench is a trapped user.
function stopResearchPolling() {
if (researchPollTimer) {
clearInterval(researchPollTimer);
@@ -13046,13 +13052,26 @@ async function renderResearchShelf() {
~${runtimeMin} min ·
${escapeHtml(((agent.research || {}).output_formats || []).join(' / '))}
-
+