diff --git a/dashboard/backend/tests/test_admin_analytics_frontend.py b/dashboard/backend/tests/test_admin_analytics_frontend.py index eead095f..976a1be4 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=159' in APP_HTML - assert 'app.js?v=160' in APP_HTML + assert 'app.js?v=161' in APP_HTML assert 'js/admin-tabs.js?v=12' in APP_HTML for tag in ( 'href="admin.css?v=13"', diff --git a/dashboard/backend/tests/test_analytics_frontend.py b/dashboard/backend/tests/test_analytics_frontend.py index 649ea82d..d6a386f4 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 47688294..80b5b4ae 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=159"' 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 05359603..df7065f3 100644 --- a/dashboard/backend/tests/test_frontend_fast_boot.py +++ b/dashboard/backend/tests/test_frontend_fast_boot.py @@ -194,7 +194,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=160" in APP_HTML + assert "app.js?v=161" in APP_HTML assert "js/agent-editor.js?v=32" in APP_HTML assert "styles.css?v=159" in APP_HTML assert "js/leaderboard.js?v=38" in APP_HTML diff --git a/dashboard/backend/tests/test_research_guest_shelf.py b/dashboard/backend/tests/test_research_guest_shelf.py index e8290c22..7f9bf246 100644 --- a/dashboard/backend/tests/test_research_guest_shelf.py +++ b/dashboard/backend/tests/test_research_guest_shelf.py @@ -10,7 +10,7 @@ RENDER = APP[APP.index('let researchShelfRenderVersion = 0;'):APP.index('/** The Community research card', APP.index('async function renderResearchShelf()'))] -@pytest.mark.parametrize('mode', ['guest', 'owner', 'failure']) +@pytest.mark.parametrize('mode', ['guest', 'empty_owner', 'owner', 'failure']) def test_research_shelf_access_states(mode): if not shutil.which('node'): pytest.skip('Node required') @@ -23,7 +23,7 @@ def test_research_shelf_access_states(mode): const empty = {}, count = {}; const document = {getElementById:id=>({agentsGridResearch:grid,agentsEmptyResearch:empty,agentsCountResearch:count}[id])}; const agent = {template_id:'research',name:'Research demo',research:{},added:true}; -const fetchResearchAgents = async()=> {if(mode==='owner') return [agent,{...agent,name:'Unadded',added:false}]; throw {status:mode==='guest'?401:503};}; +const fetchResearchAgents = async()=> {if(mode==='empty_owner')return []; if(mode==='owner') return [agent,{...agent,name:'Unadded',added:false}]; throw {status:mode==='guest'?401:503};}; let catalogCalls=0; const API = {get:async()=>{catalogCalls++;return {templates:[agent,{name:'Trading',template_id:'trading'}]};}}; const API_BASE=''; @@ -31,19 +31,19 @@ def test_research_shelf_access_states(mode): const escapeHtml=s=>String(s??''); const openAuthModal=()=>login=true; const openResearchWorkbench=()=>workbench=true; -const communityShelfButtonHtml=()=>''; +const communityShelfButtonHtml=()=>''; RENDER -(async()=>{await renderResearchShelf();if(handler)handler();console.log(JSON.stringify({html:grid.innerHTML,display:section.style.display,login,workbench,catalogCalls,error:empty.textContent}));})().catch(e=>{console.error(e);process.exit(1)}); +(async()=>{await renderResearchShelf();if(handler)handler();console.log(JSON.stringify({html:grid.innerHTML,display:section.style.display,login,workbench,catalogCalls,error:empty.textContent,emptyHtml:empty.innerHTML,countHidden:count.hidden}));})().catch(e=>{console.error(e);process.exit(1)}); '''.replace('MODE', json.dumps(mode)).replace('RENDER', RENDER) result = json.loads(subprocess.check_output(['node', '-e', script], text=True)) assert result['display'] == '' - if mode == 'guest': - assert 'Research demo' in result['html'] - assert 'Sign in to use' in result['html'] - assert 'research-remove-btn' not in result['html'] - assert 'Trading' not in result['html'] - assert result['login'] and not result['workbench'] - assert result['catalogCalls'] == 1 + if mode in ('guest', 'empty_owner'): + assert result['html'] == '' + assert 'No research agents yet.' in result['emptyHtml'] + assert '' in result['emptyHtml'] + assert result['countHidden'] + assert not result['login'] and not result['workbench'] + assert result['catalogCalls'] == 0 elif mode == 'owner': assert 'Open workbench' in result['html'] assert 'Unadded' not in result['html'] @@ -67,7 +67,7 @@ def test_pending_guest_response_cannot_overwrite_signed_in_shelf(reject_old): const catalogStarted = new Promise(resolve=>started=resolve); const catalog = new Promise((resolve,reject)=>{resolveCatalog=resolve;rejectCatalog=reject;}); const agent = {template_id:'research',name:'Owned research',research:{},added:true}; -const fetchResearchAgents = async()=>{if(signedIn)return [agent];throw {status:401};}; +const fetchResearchAgents = async()=>{if(signedIn)return [agent];started();return catalog;}; const API = {get:()=>{started();return catalog;}}; const API_BASE=''; const templateMarketplaceShelf=()=> 'research'; @@ -80,8 +80,8 @@ def test_pending_guest_response_cannot_overwrite_signed_in_shelf(reject_old): signedIn=true; await renderResearchShelf(); const expected=grid.innerHTML; - if(REJECT)rejectCatalog(new Error('Late failure')); - else resolveCatalog({templates:[{...agent,name:'Guest research'}]}); + if(REJECT)rejectCatalog({status:401}); + else resolveCatalog([]); await oldRender; console.log(JSON.stringify({unchanged:expected===grid.innerHTML,html:grid.innerHTML,error:empty.textContent})); })().catch(e=>{console.error(e);process.exit(1)}); diff --git a/dashboard/frontend/app.html b/dashboard/frontend/app.html index e81e7b5b..c0f93bf8 100644 --- a/dashboard/frontend/app.html +++ b/dashboard/frontend/app.html @@ -2512,7 +2512,7 @@

Refund Credits purchase

- + diff --git a/dashboard/frontend/app.js b/dashboard/frontend/app.js index a49e3213..bf4ed3bb 100644 --- a/dashboard/frontend/app.js +++ b/dashboard/frontend/app.js @@ -13381,16 +13381,14 @@ async function renderResearchShelf() { const section = grid.closest('.agents-category'); if (section) section.style.display = ''; let agents = []; - let guest = false; try { try { agents = await fetchResearchAgents(); } catch (error) { if (renderVersion !== researchShelfRenderVersion) return; if (error?.status !== 401) throw error; - guest = true; - const catalog = await API.get(`${API_BASE}/api/v1/agents/marketplace`); - agents = (catalog.templates || []).filter(agent => templateMarketplaceShelf(agent) === 'research'); + // Guests share the unadded shelf state; discovery lives in Community. + agents = []; } } catch (error) { if (renderVersion !== researchShelfRenderVersion) return; @@ -13403,7 +13401,7 @@ async function renderResearchShelf() { return; } if (renderVersion !== researchShelfRenderVersion) return; - const added = guest ? agents : agents.filter((agent) => agent.added); + const added = agents.filter((agent) => agent.added); if (countEl) { countEl.textContent = String(added.length); countEl.hidden = added.length === 0; @@ -13412,7 +13410,7 @@ 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 a research agent from ' + communityShelfButtonHtml('all') + '.'; } return; @@ -13432,14 +13430,13 @@ async function renderResearchShelf() { ${escapeHtml(((agent.research || {}).output_formats || []).join(' / '))}

- - ${guest ? '' : ``} + +
`; }).join(''); grid.querySelectorAll('.research-agent-card').forEach((card) => { card.querySelector('.research-open-btn')?.addEventListener('click', () => { - if (guest) { openAuthModal('login'); return; } openResearchWorkbench(card.dataset.templateId); }); card.querySelector('.research-remove-btn')?.addEventListener('click', async (event) => {