feat: add interactive TUI for task execution - #3015
Draft
janluke wants to merge 28 commits into
Draft
Conversation
Split the runtime, Bubble Tea model, task state, and rendering code into cohesive files. Fix terminal-cell truncation and renumber repeated calls after execution joins.
This file should probably be removed before merging the PR or moved to a more appropriate location maybe.
The TUI classifies a task as canceled when its completion error wraps context.Canceled. A killed process does not report that the same way everywhere: the shell interpreter surfaces the context error on Unix, while on Windows the same kill arrives as a plain non-zero exit status. On Windows every in-flight task was therefore marked failed with a spurious "exit status 1" whenever a run was interrupted or fail-fast cancellation kicked in. Consult the context when reporting a completion so the distinction is portable, wrapping the task error rather than replacing it so its message survives for display. Fixes TestTaskLifecycleReportsFailfastCancellation on Windows.
The TUI was originally conceived as a fourth output mode, so it was built as an output.Output and installed by swapping Executor.Output at runtime. Everything else about it was then discovered by type-asserting that field: two lifecycle interfaces, a task-aware writer interface and a TerminalUI marker. That conflated two unrelated things. Output describes where bytes go; it should not also carry execution lifecycle events or a capability saying who owns the terminal. The launcher settles it: --output has to stay meaningful alongside the TUI, because tasks started with ctrl+r run with Task's normal terminal output. The two are orthogonal, so the TUI cannot be an output mode. Replace the four optional interfaces with one task.Listener and an optional Executor.Listener field. A nil Listener behaves exactly as before. internal/output goes back to describing output only. Also drop --tui from the executor's error messages, which no longer knows the listener is a TUI, and name invocations with t.Name() rather than the often-empty t.Prefix.
The TUI reuses a single Executor for every launcher selection, but the map of started executions that "run: once" and "run: when_changed" calls join is only ever populated, never cleared. Running a task, returning to the launcher and running it again found the first run's finished execution and returned its result instantly: no execution, no output, a green tick. The per-task counter behind MaximumTaskCall accumulates the same way and would eventually refuse to run a task at all in a long session. Add Executor.ResetRunState to clear both, and call it for each launcher selection. Default "run: always" tasks hash to the empty string and never reached the map, which is why this went unnoticed.
Task existence is checked inside Executor.Run, which the TUI only reaches after the Bubble Tea program has started. "task --tui nope" therefore took over the terminal and reported the error inside a dashboard the user then had to quit. Resolve the requested calls before the alt screen opens so the error lands on the terminal like any other.
The dashboard gives each invocation its own output pane, so --output does not apply to tasks run inside it, but it does apply to tasks started with ctrl+r. Say so in the CLI reference. Also note in RunTask why a call that fails to resolve or compile emits no lifecycle events and so stays out of a listener's task list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds an interactive terminal interface through
task --tui.The TUI is both a task launcher and an execution dashboard. It makes it possible to inspect the status and output of each task and subtask independently, instead of reading combined output from multiple processes in one terminal stream.
Without task arguments,
task --tuiopens a searchable launcher containing all non-internal tasks. When task arguments are supplied, execution starts directly in the dashboard.Demo
Updated to: 2026/09/03
task-tui-demo-1.5x.mp4
Motivation
The existing output modes work well for conventional terminal output, but it can be difficult to follow several tasks running concurrently. Their output is either interleaved, prefixed in a single stream, or grouped and displayed only after execution.
The TUI provides a separate view for each task invocation. The task panel shows the execution state of roots and subtasks, while the output panel displays and scrolls the output of the selected task.
This is particularly useful for orchestration tasks with multiple dependencies, parallel execution, or long-running commands that produce substantial output.
User experience
The launcher presents tasks as a compact two-column list containing the task name and description.
During execution:
fopens a fullscreen view suitable for terminal copy operations.breturns to the launcher.qor Ctrl+C exits the TUI.Returning to the launcher or quitting while execution is active requests cancellation and waits for Task's processes to exit before changing views or closing the application.
Root tasks remain selectable because they may contain their own shell commands in addition to invoking dependencies.
Additional TUI options
There are currently two options for experimentation purposes, but they are not necessary by any means.
--tui-task-navigatorThe task navigator is the left pane where running tasks are displayed. It can use a tree view (default) or a flat list view:
task --tui --tui-task-navigator list ... task --tui --tui-task-navigator tree ... # defaultIn tree mode, a task shared by multiple parents remains visible at each invocation location. Joined calls use a marker and resolve to the status and output of the invocation that owns the execution.
--tui-statusTask status can be represented by icons or text labels:
task --tui --tui-status icons ... # default task --tui --tui-status labels ...Execution behavior
Multiple task arguments are represented as independent roots in the dashboard.
They retain Task's existing execution semantics:
--parallelexecutes the requested roots concurrently.run: oncecalls share the status and output of their owninginvocation.
run: alwayscalls remain separate invocations.The dashboard distinguishes the following states:
Canceled tasks were actively interrupted. Skipped tasks were never attempted, for example because an earlier sequential root failed.
Implementation
The terminal interface lives in a dedicated
internal/tuipackage and is built using Bubble Tea, Bubbles, and Lip Gloss.The executor emits optional lifecycle events for:
Each runtime call receives an invocation ID, root ID, and parent ID. These values allow the TUI to associate output and status with individual task calls and to construct either the list or tree navigator.
Task-aware output writers associate stdout and stderr with the relevant invocation. Existing output implementations continue to use the original output interface and do not need to implement lifecycle or task-aware output support.
The launcher and dashboard run in the same terminal application. A task can finish and return to the launcher without restarting Task, while cancellation is coordinated with the executor so the TUI does not exit before child processes have returned.
Current issues/limitations
Limitations and questions for discussion:
--yes.ToDo list
task --tui <non-existent-task>, display an error message without entering the TUI.Testing
The implementation includes tests covering:
run: oncejoinsThe following checks pass:
The launcher, dashboard, multiple-root execution, text selection, and process
cancellation were also exercised manually in a pseudo-terminal.
AI usage
The code in this PR was generated by ChatGPT 5.6 Sol. I've not yet reviewed the code in depth, but I'm aware of the general architecture, I manually tested the feature and went through multiple iterations of fixes and improvements.
At the moment, this PR serves primarily as a way to prototype and iterate the TUI.
Related issue
Closes #2077.
Checklist