Skip to content

feat: add spinner feedback for long-running operations#277

Merged
avihut merged 18 commits intomasterfrom
refactor/tui
Mar 7, 2026
Merged

feat: add spinner feedback for long-running operations#277
avihut merged 18 commits intomasterfrom
refactor/tui

Conversation

@avihut
Copy link
Owner

@avihut avihut commented Mar 7, 2026

Summary

  • Add animated spinner (braille characters on stderr) to all 12 long-running commands: clone, init, checkout/go, start, prune, fetch, carry, sync, branch-delete, flow-adopt, flow-eject, worktree-branch
  • Spinners are automatically suppressed in quiet mode, non-TTY stderr, and test environments (DAFT_TESTING)
  • Fix ghost spinner lines on error paths — finish_spinner() is always called before propagating errors via ? (applied across all 13 spinner sites)
  • Force immediate first draw with explicit tick() before enable_steady_tick(), since the timer only schedules future ticks
  • Clear spinner before hook execution to prevent overlapping output
  • Add developer sandbox infrastructure (mise run sandbox, sandbox:setup, sandbox:clean) for per-worktree test environments with auto-cleanup on worktree removal
  • Show branch and commit hash in daft --version for dev builds via build.rs
  • Move default branch marker to annotation column in daft list output

Test plan

  • mise run clippy — zero warnings
  • mise run fmt:check — formatting verified
  • mise run test:unit — all 395+ tests pass
  • Manual spinner verification with testing checklist
  • Verify spinners suppressed with --quiet, piped output, and DAFT_TESTING=1
  • Verify no ghost spinner lines on error paths (e.g., daft go --start <nonexistent>)
  • Verify spinner clears before hook output appears

🤖 Generated with Claude Code

avihut and others added 18 commits March 7, 2026 10:23
Add indicatif spinner progress indicators to all commands that perform
long-running git/filesystem operations (clone, init, checkout, prune,
fetch, sync, carry, branch delete, flow adopt/eject, worktree branch).

The spinner runs during core execute() calls on stderr, with step()
messages updating the spinner text in real time. When the operation
completes, the spinner is cleared and replaced by the existing result
summary line.

Key implementation details:
- Add start_spinner/finish_spinner to Output trait
- CliOutput uses indicatif ProgressBar with braille tick characters
- Spinner is no-op in quiet mode, non-TTY, or DAFT_TESTING env
- Drop impl on CliOutput ensures spinner cleanup on early return
- All stdout/stderr writes coordinate with spinner via suspend/println
- Route gitoxide experimental notice through Output system instead of
  direct eprintln (which corrupted spinner display)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The HookRenderer writes directly to stderr, bypassing the Output
system's spinner coordination. This caused ghost spinner lines when
hooks ran during a spinner-wrapped execute() call.

Fix by calling finish_spinner() in CommandBridge::run_hook() before
delegating to the hook executor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move finish_spinner() from CommandBridge::run_hook() into the hook
executor, right before the hook header is printed. This keeps the
spinner alive during hook discovery and trust checks (which don't
produce visible output), and only clears it at the last moment before
the hook renderer starts writing.

If hooks are skipped (disabled, no files found, untrusted), the
spinner is never cleared — it stays active for the rest of the
operation.

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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add bash/zsh/fish sandbox() shell functions for automatic cd
- Update behavior to reflect .envrc regeneration on re-run
- Add _lib.sh to file structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `mise run sandbox:setup` to install a `daft-dev-sandbox` shell
function into the user's RC file for automatic cd into sandboxes.
Rename the shell function from `sandbox()` to `daft-dev-sandbox()` to
avoid conflicts. Stub `compdef` in generated .envrc to suppress zsh
completion errors under direnv. Add sandbox:clean to the
worktree-pre-remove hook for automatic cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Export DAFT_WORKTREE in the generated .envrc for easy navigation back
to the worktree. Add the sandbox directory to PATH and generate a
daft-rebuild script that builds daft from the worktree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add build.rs that embeds the git branch name and short commit hash
into the display version for dev builds. `daft --version` now shows
"1.0.33 (dev branch abc1234)" while release builds (DAFT_BUILD_RELEASE=1)
show a clean "1.0.33". Clap attributes and man pages always use the
clean version to avoid churn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
enable_steady_tick() only schedules future ticks at 80ms intervals.
Without an explicit tick() call, fast operations complete before the
first draw, making the spinner invisible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move `daft shell-init` eval from the generated .envrc (where direnv
silently drops shell functions) into the daft-dev-sandbox shell
function where it actually works. Remove the now-unnecessary compdef
stub and shell detection from the sandbox task.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `?` operator inside a block propagates from the enclosing function,
not from the block. This meant that if execute() failed, finish_spinner()
was never reached, leaving ghost spinner lines on screen.

Most visible in `daft go --start` where a failed checkout (BranchNotFound)
left the spinner text on screen before falling through to create a new
worktree.

Fix: capture the Result without `?`, call finish_spinner(), then
propagate. Applied consistently across all 13 spinner sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the ◉ indicator from after the branch name to the first
(annotation) column alongside the > current-worktree marker. Change its
color from orange to dark gray. The annotation column width is now
dynamic: hidden when no annotations exist, narrow with one type, or
wider with both, with a space separating the two slots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@avihut avihut merged commit a702801 into master Mar 7, 2026
6 checks passed
@avihut avihut deleted the refactor/tui branch March 7, 2026 09:29
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.

1 participant