From 73f8eb2616c8fdfa80e4685387937e17d7e3305f Mon Sep 17 00:00:00 2001 From: jeda Date: Wed, 4 Mar 2026 04:12:57 +0000 Subject: [PATCH] feat(bd-k1p): Add Focus Mode UI controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented Focus Mode functionality with the following features: - Added keybindings: p (pin/unpin worker), P (pin/unpin bead), F (toggle focus) - Visual indicators (📌) for pinned workers and beads - Dimmed display for non-pinned items when Focus Mode is enabled - Updated footer to show Focus Mode status and pinned items - Updated help overlay with Focus Mode documentation - Added setFocusMode methods to WorkerGrid and ActivityStream components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .beads/issues.jsonl | 6 +- src/tui/app.test.ts | 2 + src/tui/app.ts | 126 ++++++++++++++++++++++++++- src/tui/components/ActivityStream.ts | 29 +++++- src/tui/components/WorkerGrid.ts | 21 ++++- 5 files changed, 175 insertions(+), 9 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 1f9ee3c..980b761 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,5 +1,5 @@ -{"id":"bd-102","title":"Add unit tests for DependencyDag component","description":"Create unit tests for src/tui/components/DependencyDag.ts and dagUtils.ts. Test DAG rendering, node positioning, edge drawing, and interaction handling.","status":"in_progress","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-04T03:02:12.152465132Z","created_by":"coder","updated_at":"2026-03-04T03:57:31.214667470Z","source_repo":".","compaction_level":0,"original_size":0} -{"id":"bd-122","title":"Parse conversation events from NEEDLE logs","description":"Extend parser.ts to recognize and parse conversation-related events from NEEDLE logs. Extract: user prompts, assistant responses, thinking blocks, tool calls with arguments/results. Store in ConversationEvent type.","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-04T03:05:10.414540897Z","created_by":"coder","updated_at":"2026-03-04T03:05:10.414540897Z","source_repo":".","compaction_level":0,"original_size":0} +{"id":"bd-102","title":"Add unit tests for DependencyDag component","description":"Create unit tests for src/tui/components/DependencyDag.ts and dagUtils.ts. Test DAG rendering, node positioning, edge drawing, and interaction handling.","status":"closed","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-04T03:02:12.152465132Z","created_by":"coder","updated_at":"2026-03-04T04:10:25.328580820Z","closed_at":"2026-03-04T04:10:25.309762645Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0} +{"id":"bd-122","title":"Parse conversation events from NEEDLE logs","description":"Extend parser.ts to recognize and parse conversation-related events from NEEDLE logs. Extract: user prompts, assistant responses, thinking blocks, tool calls with arguments/results. Store in ConversationEvent type.","status":"in_progress","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-04T03:05:10.414540897Z","created_by":"coder","updated_at":"2026-03-04T04:11:05.705618330Z","source_repo":".","compaction_level":0,"original_size":0} {"id":"bd-123","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:** 16700s (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-03T09:01:50.527254677Z","created_by":"coder","updated_at":"2026-03-03T09:04:19.266904698Z","closed_at":"2026-03-03T09:04:19.266841038Z","source_repo":".","compaction_level":0,"original_size":0,"comments":[{"id":12,"issue_id":"bd-123","author":"Jed Arden","text":"Alternative analysis: Work IS available (22 beads in ready-queue.json). This HUMAN bead is a false positive - workers should read ready-queue.json directly. Propose closure.","created_at":"2026-03-03T09:04:19Z"}]} {"id":"bd-129","title":"Add blessed TUI tests for ActivityStream component","description":"Add unit tests for src/tui/components/ActivityStream.ts using blessed testing patterns.","status":"closed","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T14:28:18.913405189Z","created_by":"coder","updated_at":"2026-03-03T15:31:25.719997480Z","closed_at":"2026-03-03T15:31:25.690904382Z","close_reason":"completed","source_repo":".","compaction_level":0,"original_size":0,"labels":["phase-2","testing","tui"]} {"id":"bd-13y","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:** 17238s (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-03T09:10:48.326406226Z","created_by":"coder","updated_at":"2026-03-03T09:15:00.935446905Z","closed_at":"2026-03-03T09:15:00.935230202Z","source_repo":".","compaction_level":0,"original_size":0} @@ -123,7 +123,7 @@ {"id":"bd-fi7","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:** 24009s (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:03:39.335046006Z","created_by":"coder","updated_at":"2026-03-03T11:04:19.485393573Z","closed_at":"2026-03-03T11:04:19.482736466Z","close_reason":"FALSE POSITIVE: ready-queue.json has 22 beads available. Worker discovery failed to check ready-queue.json before escalating to HUMAN bead. Same pattern as bd-2c8, bd-yw5, bd-1k7, etc.","source_repo":".","compaction_level":0,"original_size":0} {"id":"bd-fpf","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:** 21704s (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-03T10:25:15.013726473Z","created_by":"coder","updated_at":"2026-03-03T10:26:46.789814780Z","closed_at":"2026-03-03T10:26:20.140101495Z","source_repo":".","compaction_level":0,"original_size":0,"comments":[{"id":27,"issue_id":"bd-fpf","author":"Jed Arden","text":"FALSE_POSITIVE: Worker failed to check ready-queue.json. Ready queue has 22 available beads. Closing per MEMORY.md pattern.","created_at":"2026-03-03T10:26:46Z"}]} {"id":"bd-jod","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:** 28580s (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-03T12:19:52.916988738Z","created_by":"coder","updated_at":"2026-03-03T12:21:10.794758741Z","closed_at":"2026-03-03T12:21:10.788522717Z","close_reason":"FALSE POSITIVE: Worker starvation alert is incorrect. Ready-queue.json shows 22 available beads with work including: bd-2zt (ALT-001), bd-2r0 (P3-007), bd-2qm (P3-003), bd-1a2 (parser tests), bd-2en (store tests). Workers should claim from ready-queue.json directly.","source_repo":".","compaction_level":0,"original_size":0} -{"id":"bd-k1p","title":"Add Focus Mode UI controls","description":"Add Focus Mode keybindings: p to pin/unpin worker, P to pin/unpin bead, F to toggle focus. Visual indicator for pinned items, dimmed display for non-pinned.","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-04T03:06:37.599761897Z","created_by":"coder","updated_at":"2026-03-04T03:07:10.740989876Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-k1p","depends_on_id":"bd-qt4","type":"blocks","created_at":"2026-03-04T03:07:10.740878918Z","created_by":"coder"}]} +{"id":"bd-k1p","title":"Add Focus Mode UI controls","description":"Add Focus Mode keybindings: p to pin/unpin worker, P to pin/unpin bead, F to toggle focus. Visual indicator for pinned items, dimmed display for non-pinned.","status":"in_progress","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-04T03:06:37.599761897Z","created_by":"coder","updated_at":"2026-03-04T04:09:50.596654259Z","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-k1p","depends_on_id":"bd-qt4","type":"blocks","created_at":"2026-03-04T03:07:10.740878918Z","created_by":"coder"}]} {"id":"bd-lj9","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:** 20887s (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-03T10:11:39.654754002Z","created_by":"coder","updated_at":"2026-03-03T10:14:47.575272726Z","closed_at":"2026-03-03T10:14:47.575071208Z","source_repo":".","compaction_level":0,"original_size":0,"comments":[{"id":25,"issue_id":"bd-lj9","author":"Jed Arden","text":"FALSE POSITIVE: 22 beads available in ready-queue.json","created_at":"2026-03-03T10:14:41Z"}]} {"id":"bd-mn8","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:** 25832s (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:34:02.591107993Z","created_by":"coder","updated_at":"2026-03-03T11:35:08.093925253Z","closed_at":"2026-03-03T11:35:08.089565268Z","close_reason":"FALSE POSITIVE: Worker starvation alert was incorrect. Ready-queue.json contains 22 available beads (bd-2zt, bd-2ed, bd-1mq, etc.). Worker discovery logic should check ready-queue.json before creating HUMAN beads. See MEMORY.md for resolution pattern.","source_repo":".","compaction_level":0,"original_size":0} {"id":"bd-msa","title":"Implement worker analytics aggregation","description":"Create module to aggregate worker performance metrics: beads per hour, average completion time, error rate, cost per bead, idle percentage. Store time-series data.","status":"in_progress","priority":4,"issue_type":"task","assignee":"coder","created_at":"2026-03-04T03:06:04.671550741Z","created_by":"coder","updated_at":"2026-03-04T03:40:24.300691642Z","source_repo":".","compaction_level":0,"original_size":0} diff --git a/src/tui/app.test.ts b/src/tui/app.test.ts index 84ed7a6..19ed577 100644 --- a/src/tui/app.test.ts +++ b/src/tui/app.test.ts @@ -82,6 +82,7 @@ vi.mock('./components/WorkerGrid.js', () => { getSelected = vi.fn(() => null); focus = vi.fn(); getElement = vi.fn(() => ({ hide: vi.fn(), show: vi.fn(), screen: { render: vi.fn() } })); + setFocusMode = vi.fn(); }, }; }); @@ -96,6 +97,7 @@ vi.mock('./components/ActivityStream.js', () => { focus = vi.fn(); getElement = vi.fn(() => ({ hide: vi.fn(), show: vi.fn(), screen: { render: vi.fn() } })); getIsPaused = vi.fn(() => false); + setFocusMode = vi.fn(); }, }; }); diff --git a/src/tui/app.ts b/src/tui/app.ts index 7ebfd92..8519dc8 100644 --- a/src/tui/app.ts +++ b/src/tui/app.ts @@ -38,6 +38,11 @@ export class FabricTuiApp { // View mode private viewMode: 'default' | 'heatmap' | 'dag' | 'replay' | 'errors' = 'default'; + // Focus mode state + private focusModeEnabled = false; + private pinnedWorkerId?: string; + private pinnedBeadId?: string; + // UI Components private headerBox!: blessed.Widgets.BoxElement; private workerGrid!: WorkerGrid; @@ -185,13 +190,41 @@ export class FabricTuiApp { left: 0, right: 0, height: 1, - content: ' [Tab] Switch [j/k] Scroll [/] Search [H] Heatmap [D] DAG [E] Errors [?] Help [q] Quit', + content: this.getFooterContent(), style: { fg: colors.muted, }, }); } + /** + * Get footer content based on current state + */ + private getFooterContent(): string { + if (this.viewMode === 'default') { + let content = ' [Tab] Switch [j/k] Scroll [/] Search [H] Heatmap [D] DAG [E] Errors'; + + // Show focus mode status + if (this.focusModeEnabled) { + content += ' {green-fg}[FOCUS MODE]{/}'; + if (this.pinnedWorkerId) { + content += ` Worker:${this.pinnedWorkerId.slice(0, 8)}`; + } + if (this.pinnedBeadId) { + content += ` Bead:${this.pinnedBeadId}`; + } + } + + content += ' [p]Pin Worker [P]Pin Bead [F]Focus'; + content += ' [?] Help [q] Quit'; + + return content; + } + + // Return default content for other views + return ' [Tab] Switch [j/k] Scroll [/] Search [H] Heatmap [D] DAG [E] Errors [?] Help [q] Quit'; + } + /** * Bind keyboard shortcuts */ @@ -259,6 +292,19 @@ export class FabricTuiApp { this.setViewMode('default'); } }); + + // Focus mode keybindings + this.screen.key(['p'], () => { + this.toggleWorkerPin(); + }); + + this.screen.key(['P'], () => { + this.toggleBeadPin(); + }); + + this.screen.key(['F'], () => { + this.toggleFocusMode(); + }); } /** @@ -431,7 +477,7 @@ export class FabricTuiApp { // Update header this.headerBox.setContent(' FABRIC - Worker Activity Monitor'); - this.footerBox.setContent(' [Tab] Switch [j/k] Scroll [/] Search [H] Heatmap [D] DAG [E] Errors [?] Help [q] Quit'); + this.footerBox.setContent(this.getFooterContent()); } this.screen.render(); @@ -447,6 +493,9 @@ export class FabricTuiApp { const stateText = state === 'playing' ? 'PLAYING' : state === 'paused' ? 'PAUSED' : state === 'ended' ? 'ENDED' : 'READY'; this.footerBox.setContent(` [${stateText}] [Space] Play/Pause [←/→] Step [↑/↓] Speed(${speed}x) [Home/End] Jump [r] Reset [Esc] Back [q] Quit`); this.screen.render(); + } else { + this.footerBox.setContent(this.getFooterContent()); + this.screen.render(); } } @@ -460,6 +509,67 @@ export class FabricTuiApp { this.workerDetail.show(); } + /** + * Toggle worker pin + */ + private toggleWorkerPin(): void { + if (this.viewMode !== 'default') return; + + const selected = this.workerGrid.getSelected(); + if (!selected) return; + + if (this.pinnedWorkerId === selected.id) { + // Unpin worker + this.pinnedWorkerId = undefined; + } else { + // Pin worker + this.pinnedWorkerId = selected.id; + } + + this.updateFooter(); + this.render(); + } + + /** + * Toggle bead pin + */ + private toggleBeadPin(): void { + if (this.viewMode !== 'default') return; + + const selected = this.workerGrid.getSelected(); + if (!selected || !selected.lastEvent?.bead) return; + + const beadId = selected.lastEvent.bead; + if (this.pinnedBeadId === beadId) { + // Unpin bead + this.pinnedBeadId = undefined; + } else { + // Pin bead + this.pinnedBeadId = beadId; + } + + this.updateFooter(); + this.render(); + } + + /** + * Toggle focus mode + */ + private toggleFocusMode(): void { + if (this.viewMode !== 'default') return; + + this.focusModeEnabled = !this.focusModeEnabled; + + // If disabling focus mode, clear pins + if (!this.focusModeEnabled) { + this.pinnedWorkerId = undefined; + this.pinnedBeadId = undefined; + } + + this.updateFooter(); + this.render(); + } + /** * Toggle help overlay */ @@ -489,11 +599,15 @@ Actions: / - Search f - Filter r - Refresh - p - Pause scroll H - Toggle file heatmap D - Toggle dependency DAG R - Toggle session replay +Focus Mode: + F - Toggle focus mode + p - Pin/unpin selected worker + P - Pin/unpin bead (from selected worker) + Heatmap View: s - Cycle sort mode c - Toggle collisions only @@ -541,6 +655,8 @@ General: private renderWorkers(): void { const workers = this.store.getWorkers(); this.workerGrid.updateWorkers(workers); + this.workerGrid.setFocusMode(this.focusModeEnabled, this.pinnedWorkerId); + this.activityStream.setFocusMode(this.focusModeEnabled, this.pinnedBeadId, this.pinnedWorkerId); } /** @@ -550,6 +666,10 @@ General: this.activityStream.addEvent(event); this.renderWorkers(); + // Update focus mode state after rendering + this.workerGrid.setFocusMode(this.focusModeEnabled, this.pinnedWorkerId); + this.activityStream.setFocusMode(this.focusModeEnabled, this.pinnedBeadId, this.pinnedWorkerId); + // Update heatmap if visible if (this.viewMode === 'heatmap') { this.fileHeatmap.updateData( diff --git a/src/tui/components/ActivityStream.ts b/src/tui/components/ActivityStream.ts index a8a3bfd..2bb4e60 100644 --- a/src/tui/components/ActivityStream.ts +++ b/src/tui/components/ActivityStream.ts @@ -54,6 +54,9 @@ export class ActivityStream { private filter: ActivityFilter = {}; private maxLines: number; private isPaused = false; + private focusModeEnabled = false; + private pinnedBeadId?: string; + private pinnedWorkerId?: string; constructor(options: ActivityStreamOptions) { this.maxLines = options.maxLines || 500; @@ -105,11 +108,23 @@ export class ActivityStream { if (event.tool) { msg = `[${event.tool}] ${msg}`; } + + // Check if this event is pinned + const isBeadPinned = this.pinnedBeadId && event.bead === this.pinnedBeadId; + const isWorkerPinned = this.pinnedWorkerId && event.worker === this.pinnedWorkerId; + const isPinned = isBeadPinned || isWorkerPinned; + const pinIndicator = isPinned ? '{yellow-fg}📌{/}' : ''; + if (event.bead) { - msg = `{blue-fg}${event.bead}{/} ${msg}`; + msg = `{blue-fg}${event.bead}{/} ${pinIndicator}${msg}`; } - return `{gray-fg}${time}{/} {bold}${workerShort}{/} {${levelColor}-fg}${event.level.toUpperCase()}{/} ${msg}`; + // Dim non-pinned events when in focus mode + const shouldDim = this.focusModeEnabled && (this.pinnedBeadId || this.pinnedWorkerId) && !isPinned; + const dimPrefix = shouldDim ? '{gray-fg}' : ''; + const dimSuffix = shouldDim ? '{/}' : ''; + + return `${dimPrefix}{gray-fg}${time}{/} {bold}${workerShort}{/} {${levelColor}-fg}${event.level.toUpperCase()}{/} ${msg}${dimSuffix}`; } /** @@ -262,6 +277,16 @@ export class ActivityStream { getFilteredEventsCount(): number { return this.events.filter(e => this.passesFilter(e)).length; } + + /** + * Set focus mode state + */ + setFocusMode(enabled: boolean, pinnedBeadId?: string, pinnedWorkerId?: string): void { + this.focusModeEnabled = enabled; + this.pinnedBeadId = pinnedBeadId; + this.pinnedWorkerId = pinnedWorkerId; + this.reRender(); + } } export default ActivityStream; diff --git a/src/tui/components/WorkerGrid.ts b/src/tui/components/WorkerGrid.ts index 0b4d77a..96a4c96 100644 --- a/src/tui/components/WorkerGrid.ts +++ b/src/tui/components/WorkerGrid.ts @@ -32,6 +32,8 @@ export class WorkerGrid { private box: blessed.Widgets.BoxElement; private workers: WorkerInfo[] = []; private selectedIndex = 0; + private focusModeEnabled = false; + private pinnedWorkerId?: string; constructor(options: WorkerGridOptions) { this.box = blessed.box({ @@ -114,7 +116,15 @@ export class WorkerGrid { const collisionIndicator = this.getCollisionIndicator(worker); const selectedMarker = isSelected ? '>' : ' '; - return `${selectedMarker} {${color}-fg}${icon}{/} {bold}${workerId}{/} {gray-fg}${currentTask}{/} ${taskDesc} {blue-fg}${duration}{/} ${collisionIndicator}`; + const isPinned = this.pinnedWorkerId === worker.id; + const pinIndicator = isPinned ? '{yellow-fg}📌{/}' : ''; + + // Dim non-pinned workers when in focus mode + const shouldDim = this.focusModeEnabled && this.pinnedWorkerId && !isPinned; + const dimPrefix = shouldDim ? '{gray-fg}' : ''; + const dimSuffix = shouldDim ? '{/}' : ''; + + return `${dimPrefix}${selectedMarker} {${color}-fg}${icon}{/} {bold}${workerId}{/} ${pinIndicator} {gray-fg}${currentTask}{/} ${taskDesc} {blue-fg}${duration}{/} ${collisionIndicator}${dimSuffix}`; } /** @@ -199,6 +209,15 @@ export class WorkerGrid { getElement(): blessed.Widgets.BoxElement { return this.box; } + + /** + * Set focus mode state + */ + setFocusMode(enabled: boolean, pinnedWorkerId?: string): void { + this.focusModeEnabled = enabled; + this.pinnedWorkerId = pinnedWorkerId; + this.render(); + } } export default WorkerGrid;