Skip to content

Improve auto branch naming ergonomics#68

Open
snoolord wants to merge 33 commits intoraine:mainfrom
snoolord:feat/auto-name-command-and-slug-input
Open

Improve auto branch naming ergonomics#68
snoolord wants to merge 33 commits intoraine:mainfrom
snoolord:feat/auto-name-command-and-slug-input

Conversation

@snoolord
Copy link
Copy Markdown

@snoolord snoolord commented Mar 2, 2026

Summary

  • allow workmux add to accept unquoted multi-word branch input and normalize it to kebab-case for ergonomic branch creation
  • add auto_name.command so --auto-name can invoke agent-specific CLIs like opencode run or claude -p instead of requiring llm
  • add regression tests covering CLI normalization, custom auto-name command execution, and config parsing for the new field

Test Plan

  • cargo test llm::tests
  • cargo test cli::tests
  • cargo test parse_auto_name_command

Infonautica and others added 30 commits February 25, 2026 10:36
* docs: add zellij screenshot to docs page

* docs: update zellij screenshot
Retina (2x) screenshots look blurry on non-retina displays because
browser downscaling produces poor results. This adds a Vite plugin that
uses sharp to generate proper half-resolution 1x WebP variants at build
time and injects srcset density descriptors into <img> tags
automatically.

The plugin has two parts:
- A transform hook that adds srcset="/_1x/foo.webp 1x, /foo.webp 2x"
  to all <img> tags with .webp src in markdown files
- A buildStart hook that generates half-size images into public/_1x/
  using sharp's Lanczos resampling

Images with existing srcset or data-no-retina attributes are skipped.
No markdown files need editing -- the transform is fully automatic.
close.rs was deriving mode and constructing MuxHandle from raw CLI input
instead of resolving through git::find_worktree first. When a user ran
'workmux close feature/foo' but the worktree handle was 'api-foo', it
would look up the wrong mode (workmux.worktree.feature/foo.mode instead
of workmux.worktree.api-foo.mode) and construct an incorrect target name
(wm-feature/foo instead of wm-api-foo).

Now find_worktree is called during handle resolution. The actual handle
is extracted from the worktree path basename before mode lookup and
MuxHandle construction. This matches the pattern already used in
workflow::open.

Adds integration test that creates a worktree with --name (custom handle)
and closes it using the branch name to verify resolution works correctly.
When users click on any content image in the docs, the full-size
version now displays in a modal overlay with a dimmed background.
Click outside the image or press Escape to dismiss.

Implementation uses vanilla JS with document-level event delegation,
which handles VitePress SPA route changes automatically without
rebinding listeners. Images are filtered to only zoom .vp-doc content
images, excluding SVGs and linked images. Retina srcset attributes
are preserved in the zoomed view.

Includes smooth open/close CSS animations (fade + scale) and body
scroll lock while the overlay is open.
- Click on zoomed image now closes the overlay (was blocked before,
  creating confusing UX with zoom-out cursor)
- SVG exclusion uses regex to handle query params/hashes in URLs
- Add popstate listener to close overlay on browser back/forward
- Guard against double-close race condition during animation
- Use window sentinel instead of module var for HMR safety
- Don't copy srcset to modal so full-size image always displays
  (on DPR=1 screens, srcset 1x candidate would be half-size)
wait-timeout was listed in Cargo.toml but never imported or used
anywhere in the codebase. Removing dead dependency.
strip-ansi-escapes was used in a single call site (capture.rs).
The console crate, already a dependency, provides strip_ansi_codes
which does the same thing. Removes a redundant dependency.
fs_extra was used in a single file (setup.rs) for copying files and
directories into new worktrees. Replaced with std::fs::copy for files
and a small copy_dir_recursive helper for directories.

The helper properly handles:
- Overwriting existing files/symlinks at the destination
- Preserving symlinks (avoids infinite recursion on symlink loops)
- Skipping special files like sockets/FIFOs (avoids blocking)
Add Apple Container (`container` binary) as a third container runtime
alongside Docker and Podman. This enables sandboxing on macOS using
Apple's native container/VM technology (macOS 26+, Apple Silicon).

Key changes:

- Add `AppleContainer` variant to `SandboxRuntime` with serde name
  `apple-container` (alias `apple`)
- Add capability methods on the enum (`binary_name`, `needs_add_host`,
  `needs_userns_keep_id`, `needs_deny_mode_caps`, `pull_args`,
  `supports_file_mounts`) to replace match blocks throughout
- Auto-detection prefers `container` > `docker` > `podman` on macOS
  (gated behind `cfg!(target_os = "macos")` to avoid false positives)
- Skip freshness checking for Apple Container (no Docker-buildx
  equivalent available)
- Store runtime in StateStore marker files for cleanup correctness,
  so containers are stopped with the correct binary even if config
  changes between start and stop
- `stop_containers_for_handle` groups by stored runtime, no longer
  takes a config parameter
- `list_containers` returns `Vec<(String, SandboxRuntime)>` with
  tolerant parsing (empty marker files default to Docker for backwards
  compatibility)
- `sandbox shell --exec` reads stored runtime from state instead of
  current config
- Apple Container only supports directory mounts (virtiofs), so Claude
  config uses `~/.claude-sandbox-config/claude.json` with a directory
  mount and in-container symlink, while Docker/Podman keep the existing
  `~/.claude-sandbox.json` file mount
- `rpc_host` defaults to `192.168.64.1` for Apple Container
- Apple Container uses `image pull` subcommand (vs `pull` for others)
- Skip `--cap-add=NET_ADMIN` and `--security-opt` in deny mode since
  Apple Container VMs already have full capabilities as root
Add `workmux update` that downloads and installs the latest release
binary from GitHub Releases. Uses curl/tar subprocesses to avoid adding
HTTP or archive dependencies.

The update flow:
- Fetches latest version from GitHub API
- Downloads the platform-appropriate tar.gz and .sha256 checksum
- Verifies SHA-256 integrity before installing
- Replaces the binary atomically with rollback on failure
- Detects Homebrew installs and directs to `brew upgrade` instead

Spinner shows progress through each phase and cleanly reports
success or failure.
On CLI startup, workmux now reads a local cache file to check if a
newer version is available and shows a one-line stderr notice at most
once per 24 hours. A detached background process (`_check-update`)
refreshes the cache by querying the GitHub API, keeping the main
command non-blocking.

Design choices:
- Detached subprocess avoids blocking the main command
- Cache at ~/.cache/workmux/update_check.json prevents repeated API calls
- Numeric version comparison (not string equality) handles 0.1.10 > 0.1.9
- Suppressed in non-interactive contexts (piped stdout/stderr)
- Background curl has 5s connect / 10s max timeout
- Spawn failure doesn't suppress future checks (no silent blackout)

Disable via config (`auto_update_check: false`) or environment
(`WORKMUX_NO_UPDATE_CHECK=1`).
Only check for updates during `workmux add` to avoid unexpected
output on other commands. This is the most common entry point and
keeps the notification surface minimal.
jq is a fundamental CLI tool that hooks and scripts commonly depend
on. Including it in the base image ensures all agent containers
(claude, codex, gemini, opencode) have it available without needing
per-image installs.
Under parallel pytest load (12 workers), tmux pane startup and script
execution can exceed the previous 2-second timeouts. Bump wait_for_file
default from 2.0s to 5.0s and run_workmux_command timeout from 5.0s to
10.0s. Remove explicit timeout=2.0 overrides in test_agents.py so they
inherit the new default.
Allow TUI agents (Claude Code, opencode) inside sandboxed containers/VMs
to paste images via Ctrl+V by proxying clipboard reads to the host.

Built-in wl-paste and xclip shims translate Linux clipboard tool CLIs
into `workmux clipboard-read` calls. A ClipboardRead RPC request triggers
the host to read the clipboard (osascript on macOS, wl-paste/xclip on
Linux), write the PNG to the shared worktree filesystem (.workmux/tmp/),
and return the absolute path. The guest reads the file, writes raw bytes
to stdout, and deletes it. No binary data travels over JSON-lines RPC.

Key details:
- Only image/png supported for v1
- Clipboard shims use separate ClipboardRead RPC, not host-exec Exec RPC
- Shims are regular script files (not symlinks to _shim dispatcher)
- 20 MB size limit, stale file pruning >1 hour
- xclip write mode rejected with error message
Apple Container's `container build` doesn't resolve "." correctly when
the process working directory is set via current_dir. Pass the absolute
context path as a positional argument instead, which works with both
Apple Container and Docker/Podman.
SOPS stores age secret keys and PGP material in ~/.config/sops/.
A compromised guest build file could exfiltrate these via an
allowlisted host command. Add the directory to DENY_READ_DIRS so
both Seatbelt (macOS) and bwrap (Linux) block access.
The retina images plugin's buildStart hook fires async but Vite's dev
server doesn't block module resolution on it. When index.md gets
processed, the transform hook injects /_1x/ image references before
generateRetina1x has finished creating the files, causing import
resolution errors on first run in a new worktree.

Store the generation promise and await it in the transform hook so
markdown processing is blocked until images exist.
raine and others added 3 commits March 1, 2026 21:19
The dashboard rendered tmux style codes like #[fg=#a6e3a1] as literal
text because ratatui uses its own styling system, not tmux's. Meanwhile
the tmux status bar handled them correctly since tmux interprets its own
format codes natively.

Add a tmux style parser (parse_tmux_styles) that converts #[...] blocks
into ratatui styled spans. Change get_status_display() to return
Vec<(String, Style)> instead of (String, Color), matching the pattern
already used by git and PR column formatting.

Supported tmux attributes: fg=, bg=, default/none. Color formats: hex
(#RRGGBB), named colors (red, blue, etc.), indexed (colour0-colour255).
Malformed or unknown directives are handled gracefully.

Closes raine#66
@raine
Copy link
Copy Markdown
Owner

raine commented Mar 2, 2026

Thanks! I've included the auto_name.command idea in slightly different form. Now in main. Still thinking about the workmux add var args thing.

@raine raine force-pushed the main branch 2 times, most recently from a657335 to a475cb1 Compare April 5, 2026 11:22
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.

3 participants