Skip to content

Auto-refresh TUI task list after task completion#361

Open
mwarger wants to merge 6 commits intosubsy:mainfrom
mwarger:feat/auto-refresh-beads-on-completion
Open

Auto-refresh TUI task list after task completion#361
mwarger wants to merge 6 commits intosubsy:mainfrom
mwarger:feat/auto-refresh-beads-on-completion

Conversation

@mwarger
Copy link
Contributor

@mwarger mwarger commented Mar 17, 2026

Closes #360

Summary

  • Auto-refresh the TUI task list after a task completes, so new beads created by agents appear without pressing r
  • Add debounced tracker refresh in parallel mode on worker:completed and merge:completed events
  • Fix the r key to work in parallel mode (previously required engine which doesn't exist in parallel mode)

Details

Single mode (src/engine/index.ts): After a task completes (and after auto-commit), call this.refreshTasks() which re-queries the tracker and emits the existing tasks:refreshed event. Skipped in worker mode (forcedTask) since workers don't own the TUI.

Parallel mode (src/commands/run.tsx): Pass the tracker into runParallelWithTui and add a debounced (2s) scheduleTrackerRefresh() that queries tracker.getTasks() and stores the result in parallelState.refreshedTasks. Called on worker:completed and merge:completed.

TUI (src/tui/components/RunApp.tsx): Add parallelRefreshedTasks prop with a useEffect that updates the task list when it changes. Add onRefreshTasks prop so the r key works in parallel mode.

Tests (src/engine/index.test.ts): 3 new tests for refreshTasks() — event emission, totalTasks count accuracy, and no-op when tracker is null.

What this does NOT do

  • No file watching on .beads/beads.db
  • No polling timer
  • No new event types (reuses existing tasks:refreshed pipeline)
  • No changes to the tracker plugin interface

Reproducing locally

To test this yourself, create test beads that simulate an agent creating new beads during execution:

# Create an epic with a child task whose agent will create a new bead
br create "Auto-refresh test epic" --type epic --priority 2
br create "Create a sibling bead" --type task --parent ralph-tui-sqt5 --priority 2 \
  --description "When executed, run: br create \"Auto-created bead\" --type task --parent ralph-tui-sqt5 --priority 2"

Then run ralph-tui against the ralph-tui-sqt5 epic. When the agent executes ralph-tui-sqt5.1, it should create a new bead via br create. Before this PR, the new bead won't appear in the TUI until you press r. After this PR, it appears automatically.

To clean up afterward:

br delete ralph-tui-sqt5.2
br delete ralph-tui-sqt5.1
br delete ralph-tui-sqt5

Test plan

  • Single mode: run with a bead whose agent creates a new bead via br create — new bead should appear in TUI without pressing r
  • Parallel mode: same scenario — new beads should appear ~2s after worker completes
  • Manual refresh: press r in both single and parallel mode to confirm it still works
  • bun run typecheck && bun run build passes
  • bun test src/engine/index.test.ts — 22 tests pass (3 new)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Task list refresh capability during parallel execution for real-time status updates
    • Automatic task refresh triggered upon completion of worker and merge operations
    • Manual refresh option in the parallel task UI
    • Enhanced task status tracking with dynamic list updates
  • Tests

    • Comprehensive test coverage added for task refresh functionality

@vercel
Copy link

vercel bot commented Mar 17, 2026

@mwarger is attempting to deploy a commit to the plgeek Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4cff3482-76f7-4976-9954-0089139ff2ac

📥 Commits

Reviewing files that changed from the base of the PR and between 0947319 and dd5e0aa.

📒 Files selected for processing (3)
  • src/commands/run.tsx
  • src/engine/index.ts
  • src/tui/components/RunApp.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tui/components/RunApp.tsx
  • src/engine/index.ts

Walkthrough

The PR implements automatic task list refresh in the TUI after task completion. It adds post-completion tracker refresh to the engine (with debouncing in parallel mode), propagates refreshed tasks through component props, and enables the UI to recompute and display updated tasks.

Changes

Cohort / File(s) Summary
Engine tracker refresh
src/engine/index.ts, src/engine/index.test.ts
Adds refreshTasks() invocation after task completion to pick up new beads created by agents; includes try/catch wrapping and tests validating task refresh emission and status filtering (open/in_progress).
Parallel run wiring
src/commands/run.tsx
Extends RunAppWrapperProps with parallelRefreshedTasks: TrackerTask[] and onRefreshTasks: () => void; updates runParallelWithTui() to accept optional tracker parameter; adds debounced scheduleTrackerRefresh() invoked on worker:completed and merge:completed events; wires refresh callbacks to RunApp.
TUI component integration
src/tui/components/RunApp.tsx
Adds parallelRefreshedTasks and onRefreshTasks props to RunAppProps; introduces useEffect to recompute tasks when refreshed list changes; extends keyboard handling to invoke onRefreshTasks on "r" key press in parallel mode.

Sequence Diagram

sequenceDiagram
    participant Engine
    participant Tracker
    participant RunApp as TUI/RunApp
    participant UI as UI Render

    Engine->>Tracker: Task completed, call refreshTasks()
    activate Tracker
    Tracker->>Tracker: Fetch tasks (status: open|in_progress|completed)
    Tracker->>Engine: Emit tasks:refreshed event with TrackerTask[]
    deactivate Tracker
    
    Engine->>RunApp: Update parallelRefreshedTasks prop
    activate RunApp
    RunApp->>RunApp: useEffect triggers on refreshedTasks change
    RunApp->>RunApp: convertTasksWithDependencyStatus()
    RunApp->>UI: Re-render with updated task list
    deactivate RunApp
    
    UI->>UI: Display new beads in task list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarises the main change: auto-refreshing the TUI task list after task completion, which is the primary objective of the changeset.
Linked Issues check ✅ Passed The pull request successfully implements all objectives from #360: auto-refresh in single mode via engine.refreshTasks(), debounced refresh in parallel mode via tracker.getTasks(), manual refresh in parallel mode via onRefreshTasks, and uses existing tracker abstraction and tasks:refreshed pipeline.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives from #360. No file watching, polling, new event types, or tracker interface changes are introduced, remaining within specified scope.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 19.14894% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.31%. Comparing base (fcea670) to head (dd5e0aa).

Files with missing lines Patch % Lines
src/commands/run.tsx 0.00% 34 Missing ⚠️
src/engine/index.ts 69.23% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #361      +/-   ##
==========================================
- Coverage   47.35%   47.31%   -0.04%     
==========================================
  Files         111      111              
  Lines       36446    36492      +46     
==========================================
+ Hits        17258    17267       +9     
- Misses      19188    19225      +37     
Files with missing lines Coverage Δ
src/engine/index.ts 58.37% <69.23%> (+0.07%) ⬆️
src/commands/run.tsx 15.24% <0.00%> (-0.15%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/commands/run.tsx (1)

2197-2211: Clear refreshTimer during cleanup to avoid unnecessary work after shutdown.

The timer is not cleared when the TUI shuts down (see cleanup effect at lines 2617-2620). While the code is safe due to the triggerRerender?.() null-check, the async callback could still execute after cleanup begins, querying the tracker unnecessarily.

♻️ Suggested fix

Add timer cleanup in the useEffect cleanup function around line 2617:

     return () => {
       triggerRerender = null;
       clearInterval(pollInterval);
+      if (refreshTimer) {
+        clearTimeout(refreshTimer);
+        refreshTimer = null;
+      }
     };

Note: Since refreshTimer is declared outside the component, you may need to adjust the scope or use a ref pattern if you want the cleanup to access it reliably.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/run.tsx` around lines 2197 - 2211, The debounced tracker refresh
timer (refreshTimer used by scheduleTrackerRefresh) isn't cleared during
component cleanup, so add cleanup logic to clearTimeout(refreshTimer) and set
refreshTimer = null to prevent the async callback from running after shutdown;
if refreshTimer's scope prevents access from the useEffect cleanup, refactor
refreshTimer into a React ref (e.g., useRef) or make it component-scoped so the
cleanup in the effect that currently runs at lines ~2617–2620 can call
clearTimeout and null out the ref, and keep existing checks
(tracker/triggerRerender) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/hooks/dcg.json:
- Around line 7-8: The hook config currently hard-codes a user-specific binary
path for the "bash" and "powershell" entries, which will break on other
machines/CI; update the .github/hooks/dcg.json entries for "bash" and
"powershell" to use a portable command (e.g., just "dcg",
"./node_modules/.bin/dcg", or an env-driven path) so the hook resolves in
different environments; ensure the chosen portable value is consistent for both
the "bash" and "powershell" keys and documented in the repo readme or
contributing guide.

In `@src/engine/index.ts`:
- Around line 1338-1342: The call to await this.refreshTasks() inside the block
guarded by taskCompleted && !this.forcedTask can throw and currently bubbles up,
causing a completed iteration to be marked failed; make this refresh best-effort
by wrapping the call in a try/catch around refreshTasks() (inside the same
conditional), swallow the error (or log it via the existing
logger/processLogger) and do not rethrow so the iteration remains successful
even if refreshTasks() fails; update the block referencing taskCompleted,
forcedTask, and refreshTasks accordingly.

In `@src/tui/components/RunApp.tsx`:
- Around line 575-579: The effect currently ignores empty refresh results
because it only updates state when parallelRefreshedTasks.length > 0; change the
condition in the useEffect so that it applies converted tasks even when the
array is empty by checking for null/undefined instead (e.g., if
parallelRefreshedTasks != null) and then calling
setTasks(convertTasksWithDependencyStatus(parallelRefreshedTasks)); this
involves editing the useEffect that references parallelRefreshedTasks, setTasks,
and convertTasksWithDependencyStatus to remove the length>0 guard but still
avoid running on null/undefined.
- Around line 2360-2362: handleKeyboard currently calls onRefreshTasks but the
useCallback dependency array for handleKeyboard doesn't include onRefreshTasks;
update the dependency array of the useCallback that defines handleKeyboard to
include onRefreshTasks so the callback always references the latest prop (keep
the existing optional check and invocation logic intact).

---

Nitpick comments:
In `@src/commands/run.tsx`:
- Around line 2197-2211: The debounced tracker refresh timer (refreshTimer used
by scheduleTrackerRefresh) isn't cleared during component cleanup, so add
cleanup logic to clearTimeout(refreshTimer) and set refreshTimer = null to
prevent the async callback from running after shutdown; if refreshTimer's scope
prevents access from the useEffect cleanup, refactor refreshTimer into a React
ref (e.g., useRef) or make it component-scoped so the cleanup in the effect that
currently runs at lines ~2617–2620 can call clearTimeout and null out the ref,
and keep existing checks (tracker/triggerRerender) intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a695a9b1-1283-4baa-86da-7d0e03025d4c

📥 Commits

Reviewing files that changed from the base of the PR and between fcea670 and 0947319.

📒 Files selected for processing (6)
  • .beads/issues.jsonl
  • .github/hooks/dcg.json
  • src/commands/run.tsx
  • src/engine/index.test.ts
  • src/engine/index.ts
  • src/tui/components/RunApp.tsx

mwarger and others added 3 commits March 16, 2026 23:13
.github/hooks/dcg.json contained hardcoded local paths and was
not intended to be checked in.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap refreshTasks() in try/catch so refresh failures don't mark
  completed iterations as failed
- Accept empty refresh results to avoid showing stale tasks
- Add onRefreshTasks to handleKeyboard dependency array to fix stale closure
- Clear refreshTimer on cleanup to prevent post-shutdown timer fires

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI task list doesn't update when agents create new beads during execution

1 participant