Skip to content

feat(sync,prune): hook lifecycle in TUI with verbose job sub-rows#290

Merged
avihut merged 32 commits intomasterfrom
fix/hooks-in-sync-tui
Mar 11, 2026
Merged

feat(sync,prune): hook lifecycle in TUI with verbose job sub-rows#290
avihut merged 32 commits intomasterfrom
fix/hooks-in-sync-tui

Conversation

@avihut
Copy link
Owner

@avihut avihut commented Mar 11, 2026

Summary

  • Hook execution in TUI mode: Hooks (pre-create, post-create, pre-remove, post-remove) now run during daft sync and daft prune TUI mode, with real-time status display as sub-rows beneath each worktree
  • Generic executor framework: Replaces the monolithic yaml_executor with a three-layer architecture (JobSpec -> DAG scheduler -> command runner) that supports both CLI and TUI presenters
  • Verbose mode (-v): Shows hook sub-rows with orange accent-colored names, and nested job sub-rows with status-colored names (green/red/yellow), duration, and tree-drawing connectors
  • Pruned worktree strikethrough: Pruned rows display with continuous strikethrough from Branch column onwards, rendered as a single overlay line that bridges column separator gaps
  • Two-tier verbosity (-v / -vv): -v shows inline hook/job progress in TUI; -vv falls back to full sequential CLI output

Test plan

  • mise run test:unit — 618 tests pass (612 lib + 6 security)
  • mise run clippy — zero warnings
  • mise run fmt:check — clean
  • Manual: daft sync -v in a repo with hooks having multiple jobs — verify hook names orange, job sub-rows visible with correct status colors and durations
  • Manual: daft prune after deleting remote branches — verify pruned rows have continuous strikethrough
  • Manual: daft sync (no -v) — verify non-verbose mode is unchanged
  • Manual: daft sync -vv — verify full sequential CLI output fallback

🤖 Generated with Claude Code

avihut and others added 30 commits March 11, 2026 12:13
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements ProgressSink (no-op) and HookRunner via HookExecutor,
forwarding HookStarted/HookCompleted DagEvents through the DAG channel.
Skipped hooks produce no events; abort-mode failures are caught and
surfaced as failed events rather than propagating the error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- execute_prune_task now accepts hooks_config and tx parameters and
  uses TuiBridge so hook events are forwarded to the TUI renderer
- handle_post_tui_deferred now accepts hooks_config and uses
  CommandBridge so hooks run with full CLI output after TUI exits
- Remove NullBridge import (no longer used in this file)

WIP: call sites in prune.rs and sync.rs not yet updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add exit_code to HookCompleted event and HookSummaryEntry, fix post-TUI
summary format to show exit codes and 'Prune was aborted' line, add
clarifying comment for warned flag semantics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that worktree-pre-remove hooks are executed when
git-worktree-prune removes a worktree whose remote branch was deleted.

The test creates a feature branch that inherits .daft/hooks/ from main,
trusts the repository, deletes the remote branch, and runs prune. It then
asserts that the pre-remove hook marker file was created with the correct
DAFT_BRANCH_NAME.

This covers both the sequential path (CommandBridge, always executed hooks)
and proves the overall wiring works after the NullBridge→TuiBridge fix that
enables hooks in TUI mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…w rendering

Two rendering issues fixed:

1. Hook status labels ("worktree-pre-remove") were wider than STATUS_MAX_WIDTH,
   causing the Status column to dynamically expand and garble the table layout.
   Use short labels ("pre-remove") instead and bump STATUS_MAX_WIDTH to 13.

2. Hook sub-rows had only 1 cell but the table has multiple columns, causing
   ratatui to squish content into the Status column width. Sub-rows now have
   the correct number of cells (empty cells for non-Status columns).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Foundation for the three-layer separation architecture that decouples
job execution from presentation. Introduces generic types (JobSpec,
ExecutionMode, NodeStatus, JobResult), a JobPresenter trait with
NullPresenter for tests, and a CliPresenter wrapping HookRenderer
with interior Mutex for thread safety.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add migration note on NodeStatus to replace TaskStatus
- Clarify ExecutionMode docs re: DAG mode and hooks Follow variant
- Use .lock().expect() instead of .unwrap() for clearer panic messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shell command spawning logic from hooks/yaml_executor/command.rs
into src/executor/command.rs with a hooks-independent CommandResult type.
The old functions become thin wrappers that build the hook environment
and delegate to the new generalized functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l execution

Extract domain-agnostic DAG scheduling from hooks and sync implementations
into a reusable DagGraph that handles ordering, failure cascading, and
thread coordination via closure-based task execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement run_jobs() that combines JobSpec execution with presenter
notifications. Routes to sequential, piped (stop-on-failure), parallel,
or DAG-ordered execution based on mode and dependencies. Includes
real-time output streaming via reader threads and comprehensive tests
(33 unit tests covering all execution modes, presenter events, and
edge cases).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bridge between the hooks input layer (YAML JobDef, legacy script paths)
and the generic executor. yaml_jobs_to_specs handles command resolution,
env merging, working dir, RC sourcing, and template substitution.
scripts_to_specs converts legacy hook scripts into JobSpec values.
Group jobs are skipped for now (to be handled in full migration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace direct process spawning in HookExecutor::execute_legacy() with
scripts_to_specs() + run_jobs(), delegating to the new generic executor
pipeline. The execute() signature now accepts Arc<dyn JobPresenter> so
callers control presentation.

Key changes:
- execute() takes an Arc<dyn JobPresenter> parameter
- Legacy path uses job_adapter::scripts_to_specs() + runner::run_jobs()
- Remove execute_hook_file_with_renderer() and wait_with_timeout()
- Remove unused imports (BufRead, BufReader, Command, Stdio, Duration)
- CLI callers pass CliPresenter::auto(), TUI bridge passes NullPresenter
- CommandBridge stores HookOutputConfig for presenter construction
- try_yaml_hook() accepts presenter (unused for now, future migration)
- All 4 executor.rs tests updated to pass NullPresenter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements JobPresenter for the TUI renderer by forwarding phase-level
lifecycle events (HookStarted, HookCompleted) through an mpsc channel
instead of writing directly to stderr. Job-level events are no-ops since
the TUI shows phase-level status only, though output is accumulated for
failure reporting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TuiPresenter implements JobPresenter and sends DagEvents through the
mpsc channel in real-time, replacing the retroactive event sending that
currently happens in TuiBridge. This prevents ratatui and indicatif from
both writing to stderr simultaneously (which causes garbled output).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire TuiPresenter into TuiBridge so hook lifecycle events (HookStarted,
HookCompleted) are sent in real-time via presenter callbacks instead of
retroactively after execution completes. This eliminates the garbled TUI
rendering caused by hooks executing between retroactive event pairs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the old sequential.rs, parallel.rs, and dependency.rs modules
from yaml_executor and route YAML hook execution through the generic
executor (job_adapter + runner::run_jobs). This unifies both legacy
script hooks and YAML hooks under the same execution engine.

- Delete yaml_executor/sequential.rs, parallel.rs, dependency.rs, command.rs
- Remove ExecContext and ParallelJobData structs (no longer needed)
- Remove execute_jobs() and execute_single_job() functions
- Add presenter parameter to execute_yaml_hook_with_rc()
- Convert YAML ExecutionMode to generic executor::ExecutionMode
- Use yaml_jobs_to_specs() + run_jobs() for execution
- Convert Vec<JobResult> back to HookResult via job_results_to_hook_result()
- Forward presenter from try_yaml_hook() to the YAML execution path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
print_hook_header and print_hook_summary are no longer called after the
yaml_executor migration to the generic executor (which uses the
JobPresenter trait for progress output).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hook sub-rows were truncated because they were confined to table column
widths. Instead, render them as Paragraph overlays on empty placeholder
rows so the hook name and status can use the full terminal width.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ough

- Add ACCENT_COLOR_INDEX constant in styles.rs as single source of truth
  for the brand color (orange 208)
- Hook names in verbose TUI now render in the accent color
- Add job-level sub-rows under hooks in verbose mode with status-colored
  names, tree indentation, icons, and duration
- New DagEvent::JobStarted/JobCompleted variants carry job lifecycle
  events from TuiPresenter through the channel to TuiState
- Pruned worktree rows get strikethrough+dim from Branch column onwards
- Increase viewport budget for job sub-rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
avihut and others added 2 commits March 12, 2026 00:20
Render pruned row content (Branch column onwards) as a single overlay
Paragraph instead of per-cell styling, so the CROSSED_OUT modifier runs
unbroken through column separator gaps and ends at the last column's
text boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@avihut avihut merged commit d3812f0 into master Mar 11, 2026
6 checks passed
@avihut avihut deleted the fix/hooks-in-sync-tui branch March 11, 2026 22:38
@avihut avihut added this to the v1.1.0 milestone Mar 11, 2026
@avihut avihut added the feat New feature label Mar 11, 2026
@avihut avihut self-assigned this Mar 11, 2026
@avihut avihut added the fix Bug fix label Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat New feature fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant