Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions .claude/skills/test-forestui/SKILL.md
Original file line number Diff line number Diff line change
@@ -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]"
---
Expand Down Expand Up @@ -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)
Expand All @@ -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 <project-root> -- 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:

Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -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
12 changes: 10 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion forestui/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def ensure_tmux(
"-t",
f"={session_name}",
"-n",
"forestui",
get_window_name(dev_mode),
forestui_cmd,
],
)
Expand Down
19 changes: 16 additions & 3 deletions forestui/services/tmux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down