chore(bd-38s): update beads tracking and add WorkerGrid tests

Co-Authored-By: Claude Worker <noreply@anthropic.com>
This commit is contained in:
jeda 2026-03-03 14:58:09 +00:00
parent 469ad2cebe
commit e9fbd1e68c
2 changed files with 524 additions and 1 deletions

View file

@ -65,7 +65,7 @@
{"id":"bd-33m","title":"ALERT: Worker claude-code-glm-5-bravo has no work available","description":"# Worker Starvation Alert\n\nWorker **claude-code-glm-5-bravo** has exhausted all priorities and found zero work.\n\nThis is considered an error state - there should always be more work.\n\n## Worker State\n\n- **Executor:** claude-code-glm-5\n- **Model:** glm-5\n- **Workspace:** /home/coder/FABRIC\n- **Root Boundary:** /home/coder/FABRIC\n- **Last completion:** \n- **Beads completed:** 0\n- **Claim success rate:** %\n- **Uptime:** 25533s (h)\n- **Consecutive empty iterations:** 5\n\n## Priorities Exhausted\n\n1. ✗ Local workspace (bottoms-up): No beads in /home/coder/FABRIC or subfolders\n2. ✗ Parent exploration: No suitable workspaces found\n3. ✓ Maintenance: Completed (cleaned orphaned claims/locks)\n4. ✗ Gap analysis: false - No gaps found or created\n5. ✗ HUMAN alternatives: true - No HUMAN beads found to unblock\n\n## Discovered Workspaces\n\nTotal: 1\n\n- /home/coder/FABRIC\n\n## Required Actions\n\n1. Review discovery roots: Are all project folders being scanned?\n2. Check if projects need new features/tasks\n3. Review ROADMAP.md files across projects\n4. Enable gap analysis if disabled: `--enable-gap-analysis`\n5. Enable HUMAN alternatives if disabled\n6. Create manual beads to bootstrap work\n\n---\n*This alert was created automatically by Priority 6*","status":"closed","priority":0,"issue_type":"human","assignee":"coder","created_at":"2026-03-03T11:29:06.091774684Z","created_by":"coder","updated_at":"2026-03-03T11:32:06.241921160Z","closed_at":"2026-03-03T11:32:06.237751119Z","close_reason":"FALSE POSITIVE: Worker starvation alert is invalid. ready-queue.json contains 22 available beads (generated 2026-03-03T08:38:56Z). Workers should check ready-queue.json before escalating to HUMAN beads. Pattern: cat .beads/ready-queue.json | jq '.total_available' - if > 0, claim work instead of creating HUMAN bead.","source_repo":".","compaction_level":0,"original_size":0}
{"id":"bd-33w","title":"ALT-004: Pre-computed ready queue file","description":"For HUMAN bd-1sw. Maintain a .beads/ready-queue.json file that workers can read directly without any br commands. Background process refreshes it periodically. Script created at scripts/br-ready-queue.sh. Eliminates need for br ready entirely.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-03-03T08:30:24.162874006Z","created_by":"coder","updated_at":"2026-03-03T10:33:30.645720431Z","closed_at":"2026-03-03T10:33:29.443509872Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["alternative","discovery","worker"],"dependencies":[{"issue_id":"bd-33w","depends_on_id":"bd-1sw","type":"blocks","created_at":"2026-03-03T08:30:48.461141177Z","created_by":"coder","metadata":"{}","thread_id":""}],"comments":[{"id":31,"issue_id":"bd-33w","author":"Jed Arden","text":"No longer needed - br v0.1.20 fixes the schema bug natively.","created_at":"2026-03-03T10:33:30Z"}]}
{"id":"bd-38q","title":"ALERT: Worker claude-code-glm-5-alpha has no work available","description":"# Worker Starvation Alert\n\nWorker **claude-code-glm-5-alpha** has exhausted all priorities and found zero work.\n\nThis is considered an error state - there should always be more work.\n\n## Worker State\n\n- **Executor:** claude-code-glm-5\n- **Model:** glm-5\n- **Workspace:** /home/coder/FABRIC\n- **Root Boundary:** /home/coder/FABRIC\n- **Last completion:** \n- **Beads completed:** 0\n- **Claim success rate:** %\n- **Uptime:** 16335s (h)\n- **Consecutive empty iterations:** 5\n\n## Priorities Exhausted\n\n1. ✗ Local workspace (bottoms-up): No beads in /home/coder/FABRIC or subfolders\n2. ✗ Parent exploration: No suitable workspaces found\n3. ✓ Maintenance: Completed (cleaned orphaned claims/locks)\n4. ✗ Gap analysis: false - No gaps found or created\n5. ✗ HUMAN alternatives: true - No HUMAN beads found to unblock\n\n## Discovered Workspaces\n\nTotal: 1\n\n- /home/coder/FABRIC\n\n## Required Actions\n\n1. Review discovery roots: Are all project folders being scanned?\n2. Check if projects need new features/tasks\n3. Review ROADMAP.md files across projects\n4. Enable gap analysis if disabled: `--enable-gap-analysis`\n5. Enable HUMAN alternatives if disabled\n6. Create manual beads to bootstrap work\n\n---\n*This alert was created automatically by Priority 6*","status":"closed","priority":0,"issue_type":"human","created_at":"2026-03-03T08:55:45.765157605Z","created_by":"coder","updated_at":"2026-03-03T09:04:41.870073387Z","closed_at":"2026-03-03T09:04:41.869860465Z","source_repo":".","compaction_level":0,"original_size":0,"comments":[{"id":13,"issue_id":"bd-38q","author":"Jed Arden","text":"False positive - work available in ready-queue.json (22 beads). Same issue as bd-123.","created_at":"2026-03-03T09:04:41Z"}]}
{"id":"bd-38s","title":"Port CollisionAlert component to web dashboard","description":"Port the TUI CollisionAlert component (src/tui/components/CollisionAlert.ts) to React for the web dashboard. The backend already has /api/collisions endpoint.","status":"in_progress","priority":2,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T14:27:38.473268787Z","created_by":"coder","updated_at":"2026-03-03T14:51:17.579357200Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
{"id":"bd-38s","title":"Port CollisionAlert component to web dashboard","description":"Port the TUI CollisionAlert component (src/tui/components/CollisionAlert.ts) to React for the web dashboard. The backend already has /api/collisions endpoint.","status":"closed","priority":2,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T14:27:38.473268787Z","created_by":"coder","updated_at":"2026-03-03T14:57:38.614138148Z","closed_at":"2026-03-03T14:57:38.587034332Z","close_reason":"completed: CollisionAlert component was already fully implemented in the web dashboard. Added comprehensive test coverage (30 tests) covering rendering, severity grouping, type icons, acknowledgment functionality, detail view, worker display, title truncation, CSS classes, and selection handling. Tests pass successfully.","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
{"id":"bd-396","title":"Port DependencyDag component to web dashboard","description":"Port the TUI DependencyDag component (src/tui/components/DependencyDag.ts) to React for the web dashboard using a library like react-flow or dagre.","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-03T14:27:55.835238720Z","created_by":"coder","updated_at":"2026-03-03T14:27:55.835238720Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
{"id":"bd-3a0","title":"P4-003: Add Session Replay Feature","description":"Phase 4 Intelligence: Record and replay worker sessions. Allow stepping through events chronologically. Store session metadata in SQLite.","status":"closed","priority":4,"issue_type":"task","created_at":"2026-03-03T07:53:39.995567065Z","created_by":"coder","updated_at":"2026-03-03T07:53:39.995567065Z","closed_at":"2026-03-03T07:53:39.995567065Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["intelligence","phase-4","replay"]}
{"id":"bd-3av","title":"P4-006: File Heatmap","description":"Implement file heatmap feature - show which files are being accessed/modified most frequently by workers. Helps identify hot paths and potential bottlenecks.","status":"closed","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T13:30:47.180967166Z","created_by":"coder","updated_at":"2026-03-03T14:07:39.203982357Z","closed_at":"2026-03-03T14:07:39.186412732Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["heatmap","intelligence","phase-4"]}

View file

@ -0,0 +1,523 @@
/**
* Tests for WorkerGrid component
* @vitest-environment jsdom
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import WorkerGrid from '../src/components/WorkerGrid';
import { WorkerInfo } from '../src/types';
describe('WorkerGrid', () => {
const createMockWorker = (overrides: Partial<WorkerInfo> = {}): WorkerInfo => ({
id: 'worker-alpha',
lastSeen: new Date().toISOString(),
eventCount: 10,
status: 'active',
recentEvents: [],
...overrides,
});
const mockOnSelectWorker = vi.fn();
beforeEach(() => {
mockOnSelectWorker.mockClear();
});
afterEach(() => {
cleanup();
});
describe('rendering', () => {
it('should render empty state when no workers', () => {
render(
<WorkerGrid
workers={[]}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('Workers (0)')).toBeInTheDocument();
expect(screen.getByText('No workers detected')).toBeInTheDocument();
expect(screen.getByText('Waiting for log events...')).toBeInTheDocument();
});
it('should render worker count in header', () => {
const workers = [
createMockWorker({ id: 'worker-1' }),
createMockWorker({ id: 'worker-2' }),
createMockWorker({ id: 'worker-3' }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('Workers (3)')).toBeInTheDocument();
});
it('should render each worker card', () => {
const workers = [
createMockWorker({ id: 'worker-alpha' }),
createMockWorker({ id: 'worker-beta' }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('worker-alpha')).toBeInTheDocument();
expect(screen.getByText('worker-beta')).toBeInTheDocument();
});
});
describe('worker status display', () => {
it('should display active status', () => {
const workers = [
createMockWorker({ id: 'worker-1', status: 'active' }),
];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('active')).toBeInTheDocument();
expect(container.querySelector('.worker-status.active')).toBeInTheDocument();
});
it('should display idle status', () => {
const workers = [
createMockWorker({ id: 'worker-1', status: 'idle' }),
];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('idle')).toBeInTheDocument();
expect(container.querySelector('.worker-status.idle')).toBeInTheDocument();
});
it('should display error status', () => {
const workers = [
createMockWorker({ id: 'worker-1', status: 'error' }),
];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('error')).toBeInTheDocument();
expect(container.querySelector('.worker-status.error')).toBeInTheDocument();
});
});
describe('event count display', () => {
it('should display event count', () => {
const workers = [
createMockWorker({ id: 'worker-1', eventCount: 42 }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText(/42 events/)).toBeInTheDocument();
});
it('should display zero events', () => {
const workers = [
createMockWorker({ id: 'worker-1', eventCount: 0 }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText(/0 events/)).toBeInTheDocument();
});
});
describe('last seen formatting', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-03T12:00:00Z'));
});
afterEach(() => {
vi.useRealTimers();
});
it('should display seconds ago for recent events', () => {
const workers = [
createMockWorker({
id: 'worker-1',
lastSeen: new Date(Date.now() - 30000).toISOString(), // 30 seconds ago
}),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText(/30s ago/)).toBeInTheDocument();
});
it('should display minutes ago for events within an hour', () => {
const workers = [
createMockWorker({
id: 'worker-1',
lastSeen: new Date(Date.now() - 5 * 60 * 1000).toISOString(), // 5 minutes ago
}),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText(/5m ago/)).toBeInTheDocument();
});
it('should display hours ago for older events', () => {
const workers = [
createMockWorker({
id: 'worker-1',
lastSeen: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), // 2 hours ago
}),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText(/2h ago/)).toBeInTheDocument();
});
});
describe('worker selection', () => {
it('should call onSelectWorker with worker id when clicking unselected worker', () => {
const workers = [
createMockWorker({ id: 'worker-1' }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
fireEvent.click(screen.getByText('worker-1'));
expect(mockOnSelectWorker).toHaveBeenCalledWith('worker-1');
});
it('should deselect worker when clicking selected worker', () => {
const workers = [
createMockWorker({ id: 'worker-1' }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker="worker-1"
onSelectWorker={mockOnSelectWorker}
/>
);
fireEvent.click(screen.getByText('worker-1'));
expect(mockOnSelectWorker).toHaveBeenCalledWith(null);
});
it('should apply selected class to selected worker card', () => {
const workers = [
createMockWorker({ id: 'worker-1' }),
createMockWorker({ id: 'worker-2' }),
];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker="worker-1"
onSelectWorker={mockOnSelectWorker}
/>
);
const cards = container.querySelectorAll('.worker-card');
expect(cards[0]).toHaveClass('selected');
expect(cards[1]).not.toHaveClass('selected');
});
});
describe('collision indicators', () => {
it('should display warning emoji when worker has collision', () => {
const workers = [
createMockWorker({ id: 'worker-1', hasCollision: true }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText('⚠️')).toBeInTheDocument();
});
it('should not display warning emoji when worker has no collision', () => {
const workers = [
createMockWorker({ id: 'worker-1', hasCollision: false }),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.queryByText('⚠️')).not.toBeInTheDocument();
});
it('should apply collision class to worker card with collision', () => {
const workers = [
createMockWorker({ id: 'worker-1', hasCollision: true }),
createMockWorker({ id: 'worker-2', hasCollision: false }),
];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
const cards = container.querySelectorAll('.worker-card');
expect(cards[0]).toHaveClass('collision');
expect(cards[1]).not.toHaveClass('collision');
});
it('should display collision warning with file count', () => {
const workers = [
createMockWorker({
id: 'worker-1',
hasCollision: true,
activeFiles: ['/src/file1.ts', '/src/file2.ts', '/src/file3.ts'],
}),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.getByText(/Colliding on: 3 file\(s\)/)).toBeInTheDocument();
});
it('should not display collision warning when no active files', () => {
const workers = [
createMockWorker({
id: 'worker-1',
hasCollision: true,
activeFiles: [],
}),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.queryByText(/Colliding on:/)).not.toBeInTheDocument();
});
it('should not display collision warning when activeFiles is undefined', () => {
const workers = [
createMockWorker({
id: 'worker-1',
hasCollision: true,
activeFiles: undefined,
}),
];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(screen.queryByText(/Colliding on:/)).not.toBeInTheDocument();
});
});
describe('CSS classes', () => {
it('should apply worker-grid class to container', () => {
const { container } = render(
<WorkerGrid
workers={[]}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelector('.worker-grid')).toBeInTheDocument();
});
it('should apply worker-card class to each card', () => {
const workers = [
createMockWorker({ id: 'worker-1' }),
createMockWorker({ id: 'worker-2' }),
];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelectorAll('.worker-card').length).toBe(2);
});
it('should apply empty-state class when no workers', () => {
const { container } = render(
<WorkerGrid
workers={[]}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelector('.empty-state')).toBeInTheDocument();
});
it('should apply worker-card-header class', () => {
const workers = [createMockWorker({ id: 'worker-1' })];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelector('.worker-card-header')).toBeInTheDocument();
});
it('should apply worker-stats class', () => {
const workers = [createMockWorker({ id: 'worker-1' })];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelector('.worker-stats')).toBeInTheDocument();
});
it('should apply worker-id class to worker id span', () => {
const workers = [createMockWorker({ id: 'worker-1' })];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelector('.worker-id')).toBeInTheDocument();
});
it('should apply collision-indicator class to warning emoji', () => {
const workers = [createMockWorker({ id: 'worker-1', hasCollision: true })];
const { container } = render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
expect(container.querySelector('.collision-indicator')).toBeInTheDocument();
});
});
describe('accessibility', () => {
it('should have title attribute on collision indicator', () => {
const workers = [createMockWorker({ id: 'worker-1', hasCollision: true })];
render(
<WorkerGrid
workers={workers}
selectedWorker={null}
onSelectWorker={mockOnSelectWorker}
/>
);
const indicator = screen.getByText('⚠️');
expect(indicator).toHaveAttribute('title', 'File collision detected!');
});
});
});