X Tutup
Skip to content

feat(load): animated progress spinner for tmuxp load#1020

Merged
tony merged 6 commits intomasterfrom
progress-spinner
Mar 9, 2026
Merged

feat(load): animated progress spinner for tmuxp load#1020
tony merged 6 commits intomasterfrom
progress-spinner

Conversation

@tony
Copy link
Member

@tony tony commented Mar 8, 2026

Summary

  • Add animated progress spinner to tmuxp load with real-time build feedback as windows and panes are created
  • Add builder callbacks (on_progress, on_before_script, on_script_output, on_build_event) for decoupled progress reporting
  • Add Spinner and BuildTree classes in _progress.py with five built-in presets (default, minimal, window, pane, verbose)
  • Add --progress-format, --progress-lines, --no-progress CLI flags and corresponding TMUXP_PROGRESS* environment variables
  • Add documentation for progress display, format tokens, panel lines, and environment variables

Changes by area

Builder callbacks (workspace/builder.py)

  • on_progress: Fires after each pane creation with window/pane index context
  • on_before_script: Fires before run_before_script() execution
  • on_script_output: Receives lines from before_script via new on_line callback in run_before_script()
  • on_build_event: Structured lifecycle events (session_created, window_started, pane_created, window_finished, build_complete)

Spinner module (cli/_progress.py)

  • Spinner: Terminal spinner with ANSI-aware truncation, atexit cursor restore, scrolling output panel
  • BuildTree: Tracks build state, provides template context with progress tokens ({bar}, {progress}, {window}, etc.)
  • PROGRESS_PRESETS: Five named format strings; custom format strings also supported
  • render_bar(): Composite progress bar with window/pane segments and marching animation during before_script

CLI wiring (cli/load.py)

  • Wire all builder callbacks to Spinner in load_workspace()
  • Spinner scoped to build phase only — stops before interactive TMUX prompts
  • Non-TTY fallback: no spinner, logging flows normally

Documentation

  • docs/cli/load.md: Progress display section with presets, tokens, panel config, disabling
  • docs/configuration/environmental-variables.md: TMUXP_PROGRESS, TMUXP_PROGRESS_FORMAT, TMUXP_PROGRESS_LINES
  • docs/api/cli/progress.md: API reference for _progress module

Test plan

  • test_progress.py — spinner rendering, BuildTree state, preset resolution, format tokens, bar rendering, ANSI truncation, atexit cleanup
  • test_progress.py (workspace) — builder callback integration with real tmux fixtures
  • test_load.py--no-progress and --progress-format flag handling
  • uv run ruff check . — clean
  • uv run mypy — clean
  • uv run py.test -x — all pass

@codecov
Copy link

codecov bot commented Mar 8, 2026

Codecov Report

❌ Patch coverage is 64.55696% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.01%. Comparing base (e23569d) to head (96ede9b).
⚠️ Report is 7 commits behind head on master.

Files with missing lines Patch % Lines
src/tmuxp/cli/load.py 58.47% 42 Missing and 7 partials ⚠️
src/tmuxp/util.py 16.66% 2 Missing and 3 partials ⚠️
src/tmuxp/workspace/builder.py 93.93% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1020      +/-   ##
==========================================
- Coverage   81.08%   81.01%   -0.08%     
==========================================
  Files          28       28              
  Lines        2506     2623     +117     
  Branches      463      492      +29     
==========================================
+ Hits         2032     2125      +93     
- Misses        352      367      +15     
- Partials      122      131       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony tony changed the title feat(load): add animated progress spinner for tmuxp load feat(load): animated progress spinner for tmuxp load Mar 8, 2026
@tony tony force-pushed the progress-spinner branch from ce9e2c5 to 43e939d Compare March 8, 2026 21:36
@tony tony force-pushed the progress-spinner branch from 43e939d to ffcaf95 Compare March 8, 2026 21:57
tony added a commit that referenced this pull request Mar 8, 2026
…1017)

Add structured logging with `extra` context across all modules and
clean up output channels. Every module now declares
`logging.getLogger(__name__)` with structured keys (`tmux_session`,
`tmux_window`, `tmux_pane`, `tmux_config_path`) for filtering and
aggregation. A new `TmuxpLoggerAdapter` provides persistent identity
context for session/window/pane objects, portable across Python
3.10–3.13+.

- **Structured logging**: workspace builder emits INFO for session
  lifecycle and DEBUG for window/pane creation; finders, freezer,
  importers, loader, and validation modules emit DEBUG with config
  context; `NullHandler` added in library `__init__.py`
- **Colorama removal**: replace `colorama` runtime and type-stub
  dependencies with stdlib ANSI escape constants
- **Output channels**: route all raw `print()` calls through
  `tmuxp_echo()`; separate diagnostics (`logger.*()`) from
  user-facing output (`tmuxp_echo()`) per logging standards
- **OutputFormatter.emit_object()**: single-object JSON output for
  `ls --json` and `debug-info --json`; `Colors.format_rule()` with
  Unicode box-drawing characters
- **CLI log level**: default changed from INFO to WARNING so normal
  usage is not noisy
- **Traceback suppression**: build-failure tracebacks moved from
  user-visible `tmuxp_echo(traceback.format_exc())` to
  `logger.debug(exc_info=True)`; users see `[Error] <message>`,
  full traceback available via `--log-level debug`
- **`get_pane()` fix**: widen catch to `Exception` (matching
  `get_session`/`get_window`), preserve exception chain via
  `from e`, replace bare `print()` with structured debug log
- **Log file handler**: `setup_log_file()` replaces inline handler
  setup in `command_load()`, respects `--log-level` flag

Base PR for #1020 (animated progress spinner for `tmuxp load`).
Base automatically changed from logging to master March 8, 2026 22:00
@tony tony force-pushed the progress-spinner branch 2 times, most recently from b176f89 to b078902 Compare March 8, 2026 22:11
@tony
Copy link
Member Author

tony commented Mar 8, 2026

Code review

Found 1 issue:

  1. Documentation preset table shows wrong format strings for all 5 presets. Each preset in the table omits workspace: from the prefix — e.g., docs show Loading {session} {bar} {progress} {window} but the actual code defines "Loading workspace: {session} {bar} {progress} {window}". This mismatch applies to all five presets (default, minimal, window, pane, verbose).

tmuxp/docs/cli/load.md

Lines 164 to 170 in b078902

| Preset | Format |
|--------|--------|
| `default` | `Loading {session} {bar} {progress} {window}` |
| `minimal` | `Loading {session} [{window_progress}]` |
| `window` | `Loading {session} {window_bar} {window_progress_rel}` |
| `pane` | `Loading {session} {pane_bar} {session_pane_progress}` |
| `verbose` | `Loading {session} [window {window_index} of {window_total} · pane {pane_index} of {pane_total}] {window}` |

vs.

PROGRESS_PRESETS: dict[str, str] = {
"default": "Loading workspace: {session} {bar} {progress} {window}",
"minimal": "Loading workspace: {session} [{window_progress}]",
"window": "Loading workspace: {session} {window_bar} {window_progress_rel}",
"pane": "Loading workspace: {session} {pane_bar} {session_pane_progress}",
"verbose": (
"Loading workspace: {session} [window {window_index} of {window_total}"
" · pane {pane_index} of {pane_total}] {window}"
),
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

… and build_event callbacks

why: The builder needs to emit lifecycle events so a UI layer can render
real-time progress without coupling builder logic to display code.
what:
- Add on_progress, on_before_script, on_script_output, on_build_event
  callback parameters to WorkspaceBuilder
- Emit structured build events: session_created (with window_total,
  session_pane_total), window_started, pane_creating, window_done,
  workspace_built, before_script_started, before_script_done
- Add on_line callback to run_before_script() for capturing script output
- Add doctests for all callback types
@tony tony force-pushed the progress-spinner branch from f2b0c67 to fc41e8d Compare March 8, 2026 23:46
why: The CLI needs an animated progress display during workspace builds.
A dedicated module keeps display logic decoupled from builder and load.
what:
- Add Spinner context manager with atexit cursor restore and non-TTY fallback
- Add BuildTree for tracking build state (session, windows, panes)
- Add scrolling output panel for before_script output lines
- Add PROGRESS_PRESETS (default, minimal, window, pane, verbose) with
  format_template using {session}, {window}, {bar}, {progress}, etc.
- Add render_bar() with marching indicator during before_script
- Add SUCCESS_TEMPLATE and format_success() for persistent completion line
- Add _SafeFormatMap, ANSI-aware truncation, dynamic terminal width refresh
@tony tony force-pushed the progress-spinner branch from fc41e8d to ee73c01 Compare March 9, 2026 00:07
@tony
Copy link
Member Author

tony commented Mar 9, 2026

Code review

Found 1 issue:

  1. logger.error("workspace build failed", exc_info=True) reverts the deliberate fix in commit ed00d63d ("fix(logging[load]): downgrade logger.exception to debug with exc_info"). That commit intentionally downgraded this to logger.debug because logger.exception() / logger.error(..., exc_info=True) dumps a full Python traceback at ERROR level (visible at default WARNING threshold) alongside the user-friendly tmuxp_echo error message on the next line, causing double output. The tmuxp_echo already handles user-facing error display; the logger.error with exc_info=True re-introduces the duplicate traceback. CLAUDE.md logging standards also say: "Avoid catch-log-reraise without adding new context" and "Exception logging: Avoid logger.exception() followed by raise -- this duplicates the traceback." Here the code catches, logs at ERROR with traceback, and does not re-raise -- it shows a recovery prompt. Should remain logger.debug.

tmuxp/src/tmuxp/cli/load.py

Lines 399 to 401 in ee73c01

on_error_hook()
logger.error("workspace build failed", exc_info=True)
tmuxp_echo(cli_colors.error("[Error]") + f" {e}")

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony tony force-pushed the progress-spinner branch from ee73c01 to 92bdc95 Compare March 9, 2026 00:28
@tony
Copy link
Member Author

tony commented Mar 9, 2026

Code review

Found 1 issue:

  1. CHANGES entry references wrong PR number (#1017) instead of (#1020) for the animated progress spinner feature. The #1017 references elsewhere in the file all relate to the structured logging / colorama removal PR, not the spinner.

tmuxp/CHANGES

Lines 39 to 41 in 92bdc95

#### Animated progress spinner for `tmuxp load` (#1017)

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony tony force-pushed the progress-spinner branch 2 times, most recently from 202b49a to f7fb9f0 Compare March 9, 2026 00:47
tony added 4 commits March 8, 2026 20:07
why: Users need visual feedback during workspace builds, especially for
sessions with many windows or long before_script executions.
what:
- Add _silence_stream_handlers() to suppress StreamHandler during spinner
- Add _dispatch_build() extracting shared build/attach/error logic
- Wire Spinner.on_build_event and add_output_line to builder callbacks
- Add --progress-format / TMUXP_PROGRESS_FORMAT for preset or custom format
- Add --progress-lines / TMUXP_PROGRESS_LINES for panel height control
- Add --no-progress / TMUXP_PROGRESS=0 to disable spinner entirely
- Emit persistent success line with checkmark after successful build
- Stop spinner before interactive prompts (TMUX switch, error recovery)
…ld progress

why: The progress spinner and its CLI wiring need thorough test coverage
for BuildTree state, template rendering, bar generation, panel behavior,
and CLI flag handling.
what:
- Add tests/cli/test_progress.py covering Spinner lifecycle, BuildTree
  state transitions, template presets, bar rendering, panel lines,
  ANSI truncation, non-TTY fallback, and success output
- Add tests/workspace/test_progress.py for builder callback integration
- Update tests/cli/test_load.py for progress output assertions
why: Users need documentation for the progress spinner feature.
what:
- Add "Progress display" section to docs/cli/load.md with presets, tokens,
  panel lines, disabling, and before-script behavior
- Add TMUXP_PROGRESS, TMUXP_PROGRESS_FORMAT, TMUXP_PROGRESS_LINES to
  environmental-variables.md
- Add docs/api/cli/progress.md API reference page
why: PR 2 needs changelog entry for the progress spinner feature.
what:
- Add "Animated progress spinner" section under What's new
@tony tony force-pushed the progress-spinner branch from f7fb9f0 to 96ede9b Compare March 9, 2026 01:09
@tony
Copy link
Member Author

tony commented Mar 9, 2026

Code review

Found 1 issue:

  1. logger.debug("workspace build failed") should be logger.error — a workspace build failure stops the operation and prompts the user for recovery (kill/attach/detach). CLAUDE.md log levels say "ERROR: Failures that stop an operation." This same pattern was already corrected from DEBUG to ERROR in builder.py line 515 within this PR, but the analogous handler in load.py was not updated.

tmuxp/src/tmuxp/cli/load.py

Lines 406 to 410 in 96ede9b

except exc.TmuxpException as e:
if on_error_hook is not None:
on_error_hook()
logger.debug("workspace build failed", exc_info=True)
tmuxp_echo(cli_colors.error("[Error]") + f" {e}")

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony
Copy link
Member Author

tony commented Mar 9, 2026

Code review update: retracting previous finding

The issue I flagged (logger.debug at load.py:409) is a false positive. Upon reviewing git history:

  • Commit ed00d63d deliberately downgraded this from logger.exception to logger.debug because logger.exception() dumps tracebacks at ERROR level alongside the user-friendly tmuxp_echo() message, causing double output.
  • This differs from the builder.py case: in builder.py, the exception is caught-logged-reraised (adding context justifies ERROR). In load.py, the exception is handled — the user gets a recovery prompt via tmuxp_echo, so DEBUG-level traceback (for --log-level debug diagnostics) is the correct choice.

No issues found. Checked for bugs, git history context, and CLAUDE.md compliance.

🤖 Generated with Claude Code

@tony tony merged commit 975a3da into master Mar 9, 2026
13 checks passed
tony added a commit that referenced this pull request Mar 9, 2026
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

X Tutup