From 06bda18752f5862356b34cc3869e9a987af5be49 Mon Sep 17 00:00:00 2001 From: Cadu Date: Sun, 29 Mar 2026 13:25:16 -0300 Subject: [PATCH 1/6] Fix window renaming on reattach after forestui is killed When forestui was killed and then re-launched to reattach to an existing tmux session, it would incorrectly rename the first window (e.g., "ssh") to "forestui" in addition to creating the correct new forestui window. Root cause: TmuxService.current_window used session.active_window (the window the user is viewing) instead of the window the forestui process is actually running in. On reattach, the active window is window 1, not the newly created forestui window. Fix: Use TMUX_PANE env var to find the process's own window. Also fix the hardcoded "forestui" name in the recreated window to respect dev mode. Additionally improves the test-forestui skill with lessons learned: - Never call tmux directly, always go through tu - Use a parent bash shell for detach/reattach test flows - Check .tmux.conf for rename-window prompt behavior - Proactively test visual/behavioral changes Strengthens CLAUDE.md to require make check before pushing/opening PRs. --- .claude/skills/test-forestui/SKILL.md | 40 ++++++++++++++++++++++++--- CLAUDE.md | 12 ++++++-- forestui/cli.py | 2 +- forestui/services/tmux.py | 19 +++++++++++-- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/.claude/skills/test-forestui/SKILL.md b/.claude/skills/test-forestui/SKILL.md index 90460d5..0cf1853 100644 --- a/.claude/skills/test-forestui/SKILL.md +++ b/.claude/skills/test-forestui/SKILL.md @@ -1,6 +1,6 @@ --- name: test-forestui -description: Use tu to visually drive and test forestui in headless terminals. Invoke when developing forestui features, debugging UI issues, or verifying changes — lets you see and interact with the running app yourself. +description: Use tu to visually drive and test forestui in headless terminals. Invoke PROACTIVELY when fixing behavioral/visual bugs or developing new features — don't wait to be asked. Lint and typecheck can't catch UI regressions, wrong window names, or broken interactions. allowed-tools: Bash, Read argument-hint: "[what to test or verify]" --- @@ -35,9 +35,10 @@ If no `.tmux.conf`: defaults are prefix `Ctrl+B`, next `Ctrl+B n`, prev `Ctrl+B ## How to launch forestui **NEVER run `uv run forestui` or any tmux command without `TMUX_TMPDIR` isolation.** -The user is likely running their own tmux/forestui session right now. If you -connect to the default tmux server you will interfere with their live session — -creating windows, switching their active view, or corrupting their session state. +**NEVER call `tmux` directly from Bash** — not even with `TMUX_TMPDIR` set. The +user is likely running their own tmux/forestui session right now. Any direct +`tmux` call risks connecting to (and corrupting) their live session. ALL +interaction with the test tmux must go through `tu` commands. ```bash FUI_TEST_DIR=$(mktemp -d) @@ -48,6 +49,32 @@ tu run --name fui --env TMUX_TMPDIR=$FUI_TEST_DIR \ tu wait --name fui --text "forestui" --timeout 15000 ``` +### When you need to detach and reattach + +If your test involves detaching from tmux and re-running `forestui` (e.g., +testing reattach behavior), do NOT launch forestui directly as the `tu` process. +Instead, launch a **bash shell** inside `tu` and run forestui from it. This way, +when tmux detaches, bash gets its prompt back and you can run forestui again: + +```bash +tu run --name fui --env TMUX_TMPDIR=$FUI_TEST_DIR \ + --cwd -- env -u TMUX bash -l + +# Wait for bash prompt, then start forestui +tu wait --name fui --text "\\$" --timeout 5000 +tu type --name fui "uv run forestui" +tu press --name fui Enter + +# ... do your test, detach with Ctrl+A d ... +# After detach, bash prompt returns. Run forestui again: +tu wait --name fui --text "\\$" --timeout 5000 +tu type --name fui "uv run forestui" +tu press --name fui Enter +``` + +Why: forestui calls `os.execvp("tmux", ...)` to enter tmux. If forestui IS the +`tu` process, detaching kills the tu session (no shell to return to). + Need multiple terminals (e.g., testing grouped sessions)? Use the same `TMUX_TMPDIR` so they share the same isolated tmux server: @@ -85,6 +112,11 @@ each session is viewing. Use the bindings from `.tmux.conf`. Translate to `tu press` yourself. +**Gotcha — renaming windows.** Check `.tmux.conf` for the rename-window binding. +If it uses `command-prompt -I "#W"`, the prompt is pre-filled with the current +window name — press `Ctrl+U` to clear before typing. If it uses +`command-prompt` without `-I`, the prompt starts empty and you can type directly. + ### Text input ```bash diff --git a/CLAUDE.md b/CLAUDE.md index 6faf046..10c445f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -153,7 +153,14 @@ Each forest has its own `.forestui-config.json` state file. ## Testing -Currently no test suite. When adding tests: +**Visual/behavioral verification:** forestui is a TUI app — lint and typecheck +alone cannot catch visual bugs, wrong window names, broken interactions, or +UI regressions. When fixing behavioral/visual bugs or developing new features, +use the `test-forestui` skill to launch forestui in a headless terminal and +visually verify the fix or feature works. Do this proactively, not only when +asked. + +Currently no unit test suite. When adding tests: - Use pytest - Place tests in `tests/` directory - Add pytest to dev dependencies @@ -165,7 +172,8 @@ Version is `0.0.0` in source code. Actual versions are derived from git tags at ### Development 1. Create a branch, make changes -2. Run `make check` and test locally +2. **Always run `make check` before committing, pushing, or opening a PR.** Do not + push code that fails lint or typecheck — fix issues first. 3. Open a PR and merge to `main` Running from source (`uv run forestui`) shows version `0.0.0` and auto-enables dev mode. diff --git a/forestui/cli.py b/forestui/cli.py index a794944..4868629 100644 --- a/forestui/cli.py +++ b/forestui/cli.py @@ -112,7 +112,7 @@ def ensure_tmux( "-t", f"={session_name}", "-n", - "forestui", + get_window_name(dev_mode), forestui_cmd, ], ) diff --git a/forestui/services/tmux.py b/forestui/services/tmux.py index 17ea528..06db049 100644 --- a/forestui/services/tmux.py +++ b/forestui/services/tmux.py @@ -108,13 +108,26 @@ def session(self) -> Session | None: @property def current_window(self) -> Window | None: - """Get the current tmux window.""" - if self.session is None: + """Get the tmux window this process is running in. + + Uses the TMUX_PANE environment variable to find our own window, + rather than the session's active window — which may be a different + window when forestui is starting up in a background window. + """ + if self.server is None: + return None + pane_id = os.environ.get("TMUX_PANE") + if not pane_id: return None try: - return self.session.active_window + for sess in self.server.sessions: + for window in sess.windows: + for pane in window.panes: + if pane.pane_id == pane_id: + return window except LibTmuxException: return None + return None def rename_window(self, name: str) -> bool: """Rename the current tmux window.""" From 89b7826e2e4e82a3073e0c100d7b10ce5cca59e3 Mon Sep 17 00:00:00 2001 From: Cadu Date: Sun, 29 Mar 2026 13:28:03 -0300 Subject: [PATCH 2/6] Add CI workflow to run make check on PRs and pushes to main --- .github/workflows/check.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..bc8c745 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,24 @@ +name: Check + +on: + push: + branches: [main] + pull_request: + +jobs: + check: + name: Lint & Typecheck + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + run: uv python install + + - name: Run checks + run: make check From 2a6fd50b083034b487b9cd8cd462e6f65f1bf073 Mon Sep 17 00:00:00 2001 From: Cadu Date: Sun, 29 Mar 2026 13:29:13 -0300 Subject: [PATCH 3/6] test: deliberate type error to verify CI catches failures --- forestui/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/forestui/cli.py b/forestui/cli.py index 4868629..3cd3790 100644 --- a/forestui/cli.py +++ b/forestui/cli.py @@ -15,6 +15,7 @@ def get_window_name(dev_mode: bool = False) -> str: """Get the tmux window name based on dev mode flag.""" + x: int = "not an int" # deliberate type error if dev_mode: from datetime import datetime From 946ab2f5b97decedc4ba2c7848e935e3359823d9 Mon Sep 17 00:00:00 2001 From: Cadu Date: Sun, 29 Mar 2026 13:29:56 -0300 Subject: [PATCH 4/6] Revert deliberate type error used to verify CI failure detection --- forestui/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/forestui/cli.py b/forestui/cli.py index 3cd3790..4868629 100644 --- a/forestui/cli.py +++ b/forestui/cli.py @@ -15,7 +15,6 @@ def get_window_name(dev_mode: bool = False) -> str: """Get the tmux window name based on dev mode flag.""" - x: int = "not an int" # deliberate type error if dev_mode: from datetime import datetime From c9ec85fd825eba7e43c60d83b6e1355713b45033 Mon Sep 17 00:00:00 2001 From: Cadu Date: Sun, 29 Mar 2026 13:31:02 -0300 Subject: [PATCH 5/6] test: deliberate formatting error to verify CI catches it --- Makefile | 10 +++++++--- forestui/cli.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 5cd0cc5..6843330 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: lint typecheck check format install dev clean +.PHONY: lint typecheck format-check check format install dev clean # Run ruff linter lint: @@ -8,8 +8,12 @@ lint: typecheck: uv run mypy forestui/ -# Run all checks (lint + typecheck) -check: lint typecheck +# Check formatting without modifying files +format-check: + uv run ruff format --check forestui/ + +# Run all checks (lint + typecheck + format) +check: lint typecheck format-check # Format code with ruff format: diff --git a/forestui/cli.py b/forestui/cli.py index 4868629..8284e56 100644 --- a/forestui/cli.py +++ b/forestui/cli.py @@ -13,7 +13,7 @@ from forestui import __version__ -def get_window_name(dev_mode: bool = False) -> str: +def get_window_name(dev_mode : bool = False) -> str: """Get the tmux window name based on dev mode flag.""" if dev_mode: from datetime import datetime From 53c4ab25b98cf03b6389c54b2efeaed753e1926a Mon Sep 17 00:00:00 2001 From: Cadu Date: Sun, 29 Mar 2026 13:32:10 -0300 Subject: [PATCH 6/6] Add format check to make check and CI Adds `ruff format --check` as a `format-check` target to the Makefile, included in `make check`. CI now catches lint, type errors, and unformatted code. --- forestui/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forestui/cli.py b/forestui/cli.py index 8284e56..4868629 100644 --- a/forestui/cli.py +++ b/forestui/cli.py @@ -13,7 +13,7 @@ from forestui import __version__ -def get_window_name(dev_mode : bool = False) -> str: +def get_window_name(dev_mode: bool = False) -> str: """Get the tmux window name based on dev mode flag.""" if dev_mode: from datetime import datetime