diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 8c70534..81c5aee 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -5,7 +5,7 @@
{"id":"bd-195","title":"ALT-007: SQLite direct query fallback","description":"For HUMAN bead bd-3sh. Query beads.db directly using sqlite3 or Node.js better-sqlite3. Bypasses br CLI entirely. Requires sqlite3 CLI or npm package. Fastest access but tight coupling to schema.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-03-03T08:39:58.775979286Z","created_by":"coder","updated_at":"2026-03-03T10:33:32.997760049Z","closed_at":"2026-03-03T10:33:31.799597115Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["alternative","br","resilience","worker"],"comments":[{"id":32,"issue_id":"bd-195","author":"Jed Arden","text":"No longer needed - br v0.1.20 fixes the schema bug natively.","created_at":"2026-03-03T10:33:32Z"}]}
{"id":"bd-1a2","title":"P2: Add unit tests for parser.ts","description":"Add comprehensive unit tests for src/parser.ts covering: JSON parsing, formatEvent function, edge cases (malformed JSON, missing fields), and colorization options. Follow vitest patterns from tailer.test.ts.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-03-03T07:50:12.670624516Z","created_by":"coder","updated_at":"2026-03-03T10:45:42.655993370Z","closed_at":"2026-03-03T10:45:42.654557737Z","close_reason":"Parser tests already implemented in bd-5eh (36 tests covering parseLogLine, parseLogLines, formatEvent)","source_repo":".","compaction_level":0,"original_size":0,"labels":["parser","testing","unit-test"]}
{"id":"bd-1c6","title":"TEST-002: Add store integration tests","description":"Test Coverage: Add integration tests for EventStore - event indexing, LRU eviction, worker tracking, query performance.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-03-03T07:53:40.409846186Z","created_by":"coder","updated_at":"2026-03-03T07:53:40.409846186Z","closed_at":"2026-03-03T07:53:40.409846186Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["store","testing"]}
-{"id":"bd-1cc","title":"Port FileHeatmap component to web dashboard","description":"Port the TUI FileHeatmap component (src/tui/components/FileHeatmap.ts) to React for the web dashboard. Add corresponding API endpoint if needed.","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-03T14:27:43.759301428Z","created_by":"coder","updated_at":"2026-03-03T14:27:43.759301428Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
+{"id":"bd-1cc","title":"Port FileHeatmap component to web dashboard","description":"Port the TUI FileHeatmap component (src/tui/components/FileHeatmap.ts) to React for the web dashboard. Add corresponding API endpoint if needed.","status":"in_progress","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T14:27:43.759301428Z","created_by":"coder","updated_at":"2026-03-03T14:58:30.513770688Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
{"id":"bd-1e1","title":"P3-001: Setup Express HTTP server with static file serving","description":"Phase 3 Web Dashboard: Create Express server in src/web/server.ts that serves static files and handles WebSocket upgrade. Foundation for web dashboard.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-03-03T07:52:09.228852666Z","created_by":"coder","updated_at":"2026-03-03T10:05:21.171663977Z","closed_at":"2026-03-03T10:05:21.171457522Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["phase-3","server","web"]}
{"id":"bd-1fe","title":"Add RecoveryPanel component to web frontend","description":"Port TUI RecoveryPanel.ts to React web frontend. Create src/web/frontend/src/components/RecoveryPanel.tsx showing recovery suggestions for stuck workers.","status":"open","priority":2,"issue_type":"task","created_at":"2026-03-03T14:26:22.464306236Z","created_by":"coder","updated_at":"2026-03-03T14:26:22.464306236Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-3","web"]}
{"id":"bd-1fk","title":"P1: Add Express HTTP server for web dashboard","description":"Set up Express.js HTTP server with basic routing for web dashboard. Should serve static files and provide API endpoints for worker data. Part of Phase 3 Web Dashboard.","status":"closed","priority":1,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-03-03T07:50:12.280655428Z","created_by":"coder","updated_at":"2026-03-03T09:37:50.271173474Z","closed_at":"2026-03-03T08:51:00.693337164Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["http","phase-3","server","web"],"comments":[{"id":10,"issue_id":"bd-1fk","author":"Jed Arden","text":"Express server implemented in src/web/server.ts","created_at":"2026-03-03T08:52:43Z"}]}
diff --git a/src/web/frontend/components/ActivityStream.test.tsx b/src/web/frontend/components/ActivityStream.test.tsx
index 2252f6b..60ad15d 100644
--- a/src/web/frontend/components/ActivityStream.test.tsx
+++ b/src/web/frontend/components/ActivityStream.test.tsx
@@ -3,8 +3,8 @@
* @vitest-environment jsdom
*/
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { render, screen } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { render, screen, cleanup } from '@testing-library/react';
import ActivityStream from '../src/components/ActivityStream';
import { LogEvent } from '../src/types';
@@ -32,6 +32,10 @@ describe('ActivityStream', () => {
mockScrollTo.mockClear();
});
+ afterEach(() => {
+ cleanup();
+ });
+
describe('rendering', () => {
it('should render with empty events', () => {
render();
diff --git a/src/web/frontend/src/types.ts b/src/web/frontend/src/types.ts
index 2cb6a04..9bf1b48 100644
--- a/src/web/frontend/src/types.ts
+++ b/src/web/frontend/src/types.ts
@@ -140,3 +140,37 @@ export interface CollisionAlert {
collision: FileCollision | BeadCollision | TaskCollision;
suggestion?: string;
}
+
+// File Heatmap Types
+export type HeatLevel = 'cold' | 'warm' | 'hot' | 'critical';
+
+export interface WorkerFileContribution {
+ workerId: string;
+ modifications: number;
+ lastModified: number;
+ percentage: number;
+}
+
+export interface FileHeatmapEntry {
+ path: string;
+ modifications: number;
+ heatLevel: HeatLevel;
+ workers: WorkerFileContribution[];
+ firstModified: number;
+ lastModified: number;
+ hasCollision: boolean;
+ activeWorkers: number;
+ avgModificationInterval: number;
+}
+
+export interface FileHeatmapStats {
+ totalFiles: number;
+ totalModifications: number;
+ collisionFiles: number;
+ activeFiles: number;
+ heatDistribution: Record;
+ mostActiveDirectory: string;
+ avgModificationsPerFile: number;
+}
+
+export type HeatmapSortMode = 'modifications' | 'recent' | 'workers' | 'collisions';
diff --git a/src/web/frontend/test/CollisionAlert.test.tsx b/src/web/frontend/test/CollisionAlert.test.tsx
index 23567e0..b4b149e 100644
--- a/src/web/frontend/test/CollisionAlert.test.tsx
+++ b/src/web/frontend/test/CollisionAlert.test.tsx
@@ -3,7 +3,7 @@
* @vitest-environment jsdom
*/
-import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import CollisionAlert from '../src/components/CollisionAlert';
import { CollisionAlert as CollisionAlertData } from '../src/types';
@@ -37,6 +37,10 @@ describe('CollisionAlert', () => {
mockOnClose.mockClear();
});
+ afterEach(() => {
+ cleanup();
+ });
+
describe('rendering', () => {
it('should not render when visible is false', () => {
const { container } = render(