feat(bf-6b9f): sort workers by needle state + hide test workers toggle

WorkerGrid now sorts by WORKING > CLAIMING > SELECTING > BOOTING >
CLOSING > EXHAUSTED_IDLE > STOPPED and filters out test workers
(test-*, claude-test-*, nonexistent-*, needle-test, strand-runner,
-test-worker) by default. App header gets a toggle button to show/hide
test workers. EXHAUSTED_IDLE added to NeedleState type and propagated
to WorkerDetail and WorkerGrid state maps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-15 16:54:23 -04:00
parent 402e9340ad
commit cffeb26e79
8 changed files with 179 additions and 4 deletions

View file

@ -0,0 +1,16 @@
{
"bead_id": "bf-6b9f",
"agent": "echo-test",
"provider": null,
"model": null,
"exit_code": 0,
"outcome": "success",
"duration_ms": 0,
"input_tokens": null,
"output_tokens": null,
"cost_usd": null,
"captured_at": "2026-05-15T20:49:31.040008412Z",
"trace_format": "raw_text",
"pruned": false,
"template_version": null
}

View file

View file

@ -0,0 +1 @@
done

View file

@ -258,6 +258,7 @@ const App: React.FC = () => {
const [showGitIntegration, setShowGitIntegration] = useState(false);
const [showNarrative, setShowNarrative] = useState(false);
const [budgetBannerDismissed, setBudgetBannerDismissed] = useState(false);
const [hideTestWorkers, setHideTestWorkers] = useState(true);
// Budget alert state polled from /api/cost/summary
const [budgetSummary, setBudgetSummary] = useState<{
@ -826,6 +827,14 @@ const App: React.FC = () => {
<span className="narrative-toggle-icon">&#x1F4DD;</span>
<span className="narrative-toggle-label">Narrative</span>
</button>
<button
className={`hide-test-workers-toggle ${hideTestWorkers ? 'active' : ''}`}
onClick={() => setHideTestWorkers(prev => !prev)}
title={hideTestWorkers ? 'Test workers hidden — click to show' : 'Test workers visible — click to hide'}
>
<span className="hide-test-workers-icon">🧪</span>
<span className="hide-test-workers-label">{hideTestWorkers ? 'Hide Tests' : 'Show Tests'}</span>
</button>
{unacknowledgedAlertCount > 0 && (
<button
className="collision-alert-toggle"
@ -872,6 +881,7 @@ const App: React.FC = () => {
pinnedWorkers={pinnedWorkers}
onTogglePin={togglePinWorker}
focusModeEnabled={focusModeEnabled}
hideTestWorkers={hideTestWorkers}
/>
{showTimeline && (

View file

@ -10,6 +10,7 @@ const NEEDLE_STATE_ICONS: Record<NeedleState, string> = {
CLAIMING: '🎯',
WORKING: '●',
CLOSING: '⏹',
EXHAUSTED_IDLE: '💤',
STOPPED: '○',
};
@ -19,6 +20,7 @@ const NEEDLE_STATE_COLORS: Record<NeedleState, string> = {
CLAIMING: '#9b59b6',
WORKING: '#5cb85c',
CLOSING: '#f0ad4e',
EXHAUSTED_IDLE: '#95a5a6',
STOPPED: '#777',
};

View file

@ -7,6 +7,7 @@ const NEEDLE_STATE_LABELS: Record<NeedleState, string> = {
CLAIMING: 'CLAIMING',
WORKING: 'WORKING',
CLOSING: 'CLOSING',
EXHAUSTED_IDLE: 'EXHAUSTED',
STOPPED: 'STOPPED',
};
@ -16,9 +17,41 @@ const NEEDLE_STATE_COLORS: Record<NeedleState, string> = {
CLAIMING: '#9b59b6',
WORKING: '#5cb85c',
CLOSING: '#f0ad4e',
EXHAUSTED_IDLE: '#95a5a6',
STOPPED: '#777',
};
// Lower number = higher priority (shown first)
const NEEDLE_STATE_PRIORITY: Partial<Record<string, number>> = {
WORKING: 0,
CLAIMING: 1,
SELECTING: 2,
BOOTING: 3,
CLOSING: 4,
EXHAUSTED_IDLE: 5,
STOPPED: 6,
};
const TEST_WORKER_PATTERNS: RegExp[] = [
/^test-/,
/^claude-test-/,
/^nonexistent-/,
/^needle-test$/,
/^strand-runner$/,
/-test-worker$/,
];
function isTestWorker(id: string): boolean {
return TEST_WORKER_PATTERNS.some(pattern => pattern.test(id));
}
function stateSort(a: WorkerInfo, b: WorkerInfo): number {
const pa = a.needleState != null ? (NEEDLE_STATE_PRIORITY[a.needleState] ?? 7) : 7;
const pb = b.needleState != null ? (NEEDLE_STATE_PRIORITY[b.needleState] ?? 7) : 7;
if (pa !== pb) return pa - pb;
return a.id.localeCompare(b.id);
}
interface WorkerGridProps {
workers: WorkerInfo[];
selectedWorker: string | null;
@ -26,6 +59,7 @@ interface WorkerGridProps {
pinnedWorkers?: Set<string>;
onTogglePin?: (workerId: string) => void;
focusModeEnabled?: boolean;
hideTestWorkers?: boolean;
}
const WorkerGrid: React.FC<WorkerGridProps> = ({
@ -35,6 +69,7 @@ const WorkerGrid: React.FC<WorkerGridProps> = ({
pinnedWorkers = new Set(),
onTogglePin,
focusModeEnabled = false,
hideTestWorkers = true,
}) => {
const formatLastSeen = (timestamp: string) => {
const diff = Date.now() - new Date(timestamp).getTime();
@ -47,16 +82,20 @@ const WorkerGrid: React.FC<WorkerGridProps> = ({
};
const handlePinClick = (e: React.MouseEvent, workerId: string) => {
e.stopPropagation(); // Prevent card selection when clicking pin
e.stopPropagation();
if (onTogglePin) {
onTogglePin(workerId);
}
};
const visibleWorkers = [...workers]
.filter(w => !hideTestWorkers || !isTestWorker(w.id))
.sort(stateSort);
return (
<div className="worker-grid">
<h2>
Workers ({workers.length})
Workers ({visibleWorkers.length})
{focusModeEnabled && pinnedWorkers.size > 0 && (
<span style={{ marginLeft: '0.5rem', fontSize: '0.9rem', color: '#666' }}>
(Focus: {pinnedWorkers.size} pinned)
@ -64,7 +103,7 @@ const WorkerGrid: React.FC<WorkerGridProps> = ({
)}
</h2>
{workers.length === 0 ? (
{visibleWorkers.length === 0 ? (
<div className="empty-state">
<p>{focusModeEnabled && pinnedWorkers.size === 0
? 'No pinned workers. Pin workers to see them in Focus Mode.'
@ -76,7 +115,7 @@ const WorkerGrid: React.FC<WorkerGridProps> = ({
</p>
</div>
) : (
workers.map(worker => {
visibleWorkers.map(worker => {
const isPinned = pinnedWorkers.has(worker.id);
return (
<div

View file

@ -6,6 +6,7 @@ export type NeedleState =
| 'CLAIMING'
| 'WORKING'
| 'CLOSING'
| 'EXHAUSTED_IDLE'
| 'STOPPED';
export interface LogEvent {

View file

@ -520,4 +520,110 @@ describe('WorkerGrid', () => {
expect(indicator).toHaveAttribute('title', 'File collision detected!');
});
});
describe('test worker filtering', () => {
const testWorkerIds = [
'test-worker',
'test-alpha',
'claude-test-worker',
'claude-test-foo',
'nonexistent-worker',
'nonexistent-abc',
'needle-test',
'strand-runner',
'claude-interactive-charlie-test-worker',
];
it.each(testWorkerIds)('should hide test worker "%s" by default', (id) => {
const workers = [createMockWorker({ id })];
render(
<WorkerGrid workers={workers} selectedWorker={null} onSelectWorker={mockOnSelectWorker} />
);
expect(screen.queryByText(id)).not.toBeInTheDocument();
});
it.each(testWorkerIds)('should show test worker "%s" when hideTestWorkers=false', (id) => {
const workers = [createMockWorker({ id })];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
hideTestWorkers={false}
/>
);
expect(screen.getByText(id)).toBeInTheDocument();
});
it('should show non-test workers by default', () => {
const workers = [createMockWorker({ id: 'claude-interactive-alpha' })];
render(
<WorkerGrid workers={workers} selectedWorker={null} onSelectWorker={mockOnSelectWorker} />
);
expect(screen.getByText('claude-interactive-alpha')).toBeInTheDocument();
});
it('should reflect filtered count in header', () => {
const workers = [
createMockWorker({ id: 'worker-real' }),
createMockWorker({ id: 'test-hidden' }),
];
render(
<WorkerGrid workers={workers} selectedWorker={null} onSelectWorker={mockOnSelectWorker} />
);
expect(screen.getByText('Workers (1)')).toBeInTheDocument();
});
});
describe('worker sort order by needle state', () => {
it('should sort WORKING before STOPPED', () => {
const workers = [
createMockWorker({ id: 'stopped-worker', needleState: 'STOPPED' }),
createMockWorker({ id: 'working-worker', needleState: 'WORKING' }),
];
const { container } = render(
<WorkerGrid workers={workers} selectedWorker={null} onSelectWorker={mockOnSelectWorker} />
);
const cards = container.querySelectorAll('.worker-id');
expect(cards[0].textContent).toContain('working-worker');
expect(cards[1].textContent).toContain('stopped-worker');
});
it('should sort WORKING > CLAIMING > SELECTING > BOOTING > CLOSING > STOPPED', () => {
const workers = [
createMockWorker({ id: 'w-stopped', needleState: 'STOPPED' }),
createMockWorker({ id: 'w-booting', needleState: 'BOOTING' }),
createMockWorker({ id: 'w-claiming', needleState: 'CLAIMING' }),
createMockWorker({ id: 'w-selecting', needleState: 'SELECTING' }),
createMockWorker({ id: 'w-closing', needleState: 'CLOSING' }),
createMockWorker({ id: 'w-working', needleState: 'WORKING' }),
];
const { container } = render(
<WorkerGrid workers={workers} selectedWorker={null} onSelectWorker={mockOnSelectWorker} />
);
const cards = container.querySelectorAll('.worker-id');
const order = Array.from(cards).map(c => c.textContent?.trim());
expect(order).toEqual([
'w-working',
'w-claiming',
'w-selecting',
'w-booting',
'w-closing',
'w-stopped',
]);
});
it('should sort workers without needle state after workers with states', () => {
const workers = [
createMockWorker({ id: 'no-state' }),
createMockWorker({ id: 'w-working', needleState: 'WORKING' }),
];
const { container } = render(
<WorkerGrid workers={workers} selectedWorker={null} onSelectWorker={mockOnSelectWorker} />
);
const cards = container.querySelectorAll('.worker-id');
expect(cards[0].textContent).toContain('w-working');
expect(cards[1].textContent).toContain('no-state');
});
});
});