Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ build: ## Build the devbrain binary at the repo root (version from VERSION)

test: ## Go vet + full test suite, plus dashboard regression when Node is available
@scripts/test-guard.sh $(GOTESTFLAGS)
@if command -v node >/dev/null 2>&1; then node scripts/test-dashboard-concurrency.mjs; else echo "dashboard regression skipped (node unavailable)"; fi
@if command -v node >/dev/null 2>&1; then node scripts/test-dashboard-concurrency.mjs && node scripts/test-dashboard-brain-menu.mjs; else echo "dashboard regression skipped (node unavailable)"; fi

release: ## Manual fallback — CI releases on tag push (.github/workflows/release.yml)
GITHUB_TOKEN=$${GITHUB_TOKEN:-$$(gh auth token)} sh -c '\
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.26
1.5.27
19 changes: 14 additions & 5 deletions assets/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,19 @@ body.dragsel #moon{transform:scale(1.1)} /* moon grows during a drag
}

[hidden]{display:none!important}
.brain-controls,#brain-buttons{display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.brain-controls>span{color:var(--muted);font-size:11px}
#brain-buttons button{font-size:12px;padding:5px 9px;color:var(--muted)}
#brain-buttons button[aria-pressed=true]{color:var(--text);border-color:var(--accent);background:rgba(10,132,255,.18)}
.brain-controls{position:relative;flex:none}
.brain-controls>summary{display:flex;align-items:center;gap:6px;padding:6px 8px;min-height:32px;list-style:none;cursor:pointer;color:var(--muted);font-size:12px;border-radius:6px}
.brain-controls>summary::-webkit-details-marker{display:none}
.brain-controls>summary::after{content:"";width:5px;height:5px;border-right:1px solid;border-bottom:1px solid;transform:translateY(-2px) rotate(45deg)}
.brain-controls>summary:hover,.brain-controls[open]>summary{background:var(--panel);color:var(--text)}
#brain-count:empty{display:none}
#brain-count{font-variant-numeric:tabular-nums;color:var(--text)}
#brain-buttons{position:absolute;top:calc(100% + 6px);right:0;min-width:180px;max-width:calc(100vw - 32px);padding:4px;background:var(--panel);border:1px solid var(--line);border-radius:8px;box-shadow:0 6px 18px rgba(0,0,0,.3)}
#brain-buttons button{display:flex;align-items:center;gap:9px;width:100%;padding:8px;text-align:left;font-size:13px;background:transparent;border:0;border-radius:4px;overflow-wrap:anywhere}
#brain-buttons button:hover{background:var(--line)}
#brain-buttons button:disabled{opacity:.5;cursor:not-allowed}
.brain-check{display:inline-flex;align-items:center;justify-content:center;flex:none;width:15px;height:15px;border:1px solid var(--muted);border-radius:3px;font-size:11px}
[aria-pressed=true]>.brain-check{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
#brain-error{padding:12px 16px;border-bottom:2px solid var(--accent);background:var(--panel);font-weight:600}
#pf-prefs-brain{max-width:160px;font-size:11px;padding:3px 6px}
@media(max-width:640px){.brain-controls{width:100%;order:5}#pf-prefs-brain{max-width:110px}}
@media(max-width:640px){.brain-controls>summary,#brain-buttons button{min-height:44px}#pf-prefs-brain{max-width:110px}}
2 changes: 1 addition & 1 deletion assets/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<button data-view="profile" class="active">📊 Profile</button>
<button data-view="monitor" id="viewMonitorBtn" style="display:none">🌙 Nightshift</button>
</div>
<div id="brain-controls" class="brain-controls" role="group" aria-label="Brains" hidden><span>Brains</span><div id="brain-buttons"></div></div>
<details id="brain-controls" class="brain-controls" hidden><summary>Brains<span id="brain-count"></span></summary><div id="brain-buttons" role="group" aria-label="Visible Brains"></div></details>
<span class="sp"></span>
<div class="search"><input id="search" placeholder="Search tasks, PRs, authors…" aria-label="Search"><kbd>⌘K</kbd></div>
<select id="filterProject" aria-label="Project filter"><option value="">all projects</option></select>
Expand Down
13 changes: 12 additions & 1 deletion assets/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ function brainNotice(message=''){
$('#brain-error').textContent=text; $('#brain-error').hidden=!text;
}
function renderBrainButtons(){
const focused=$('#brain-buttons').contains(document.activeElement)?document.activeElement.dataset.brain:null;
$('#brain-controls').hidden=BRAINS.length<2;
$('#brain-buttons').innerHTML=BRAINS.map(b=>`<button type="button" data-brain="${esc(b.name)}" aria-pressed="${!HIDDEN_BRAINS.has(b.name)}" ${window.preferencesEditing?'disabled':''} title="${b.available?'Include '+esc(brainLabel(b.name))+' in this view':'Brain unavailable'}">${HIDDEN_BRAINS.has(b.name)?'○':'✓'} ${esc(brainLabel(b.name))}${b.available?'':' · Unavailable'}</button>`).join('');
const count=selectedBrains().length;
$('#brain-count').textContent=count===BRAINS.length?'':`${count}/${BRAINS.length}`;
$('#brain-controls summary').title=count===BRAINS.length?'All Brains Shown':`${count} of ${BRAINS.length} Brains Shown`;
$('#brain-buttons').innerHTML=BRAINS.map(b=>`<button type="button" data-brain="${esc(b.name)}" aria-pressed="${!HIDDEN_BRAINS.has(b.name)}" ${window.preferencesEditing?'disabled':''} title="${b.available?'Include '+esc(brainLabel(b.name))+' in this view':'Brain unavailable'}"><span class="brain-check" aria-hidden="true">${HIDDEN_BRAINS.has(b.name)?'':'✓'}</span>${esc(brainLabel(b.name))}${b.available?'':' · Unavailable'}</button>`).join('');
if(focused) [...$('#brain-buttons').querySelectorAll('button')].find(b=>b.dataset.brain===focused)?.focus();
$('#brain-buttons').querySelectorAll('button').forEach(b=>b.onclick=()=>{
if(window.preferencesEditing) return;
if(HIDDEN_BRAINS.has(b.dataset.brain)) HIDDEN_BRAINS.delete(b.dataset.brain); else HIDDEN_BRAINS.add(b.dataset.brain);
Expand All @@ -31,6 +36,12 @@ function renderBrainButtons(){
DATA.tasks=DATA.tasks.filter(brainIncluded); NS.runs=NS.runs.filter(brainIncluded); render(); if(VIEW==='monitor') renderMonitor(); load(); checkNS();
});
}
document.addEventListener('click',e=>{if(!e.composedPath().includes($('#brain-controls'))) $('#brain-controls').open=false;});
document.addEventListener('keydown',e=>{
if(e.key==='Escape' && $('#brain-controls').open){
$('#brain-controls').open=false; $('#brain-controls summary').focus();
}
});
const BRAINS_READY=(async()=>{
const response=await fetch('/api/brains'); if(!response.ok) throw new Error('Could not load brains');
BRAINS=(await response.json()).brains; renderBrainButtons(); brainNotice();
Expand Down
26 changes: 26 additions & 0 deletions scripts/test-dashboard-brain-menu.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
import vm from 'node:vm';

const source=readFileSync(new URL('../assets/dashboard.js',import.meta.url),'utf8');
const start=source.indexOf("document.addEventListener('click'");
const end=source.indexOf('const BRAINS_READY=',start);
assert.ok(start>=0 && end>start);
const handlers={}, menu={open:true,contains:()=>false};
let focused=false;
vm.runInNewContext(source.slice(start,end),{
document:{addEventListener:(event,handler)=>handlers[event]=handler},
$:selector=>selector==='#brain-controls'?menu:{focus:()=>{focused=true;}},
});

// Selecting a brain replaces the clicked button before the event reaches document.
handlers.click({target:{},composedPath:()=>[{},menu,{}]});
assert.equal(menu.open,true,'Selecting a brain must leave the menu open');
handlers.click({target:{},composedPath:()=>[{}]});
assert.equal(menu.open,false,'Clicking outside must close the menu');
menu.open=true;
handlers.keydown({key:'Tab'});
assert.equal(menu.open,true);
handlers.keydown({key:'Escape'});
assert.equal(menu.open,false);
assert.equal(focused,true,'Escape must return focus to the menu trigger');
Loading