Personal Herdr workflow plugin. Scatterer 0.2.0 requires Herdr 0.7.5 or newer.
The daniel.scatterer.apply-layout action creates a new Herdr workspace/space
from the currently focused pane's cwd, then uses Herdr's declarative
layout.apply socket API to make an agent tab with pi on the left and
hunk diff <parent-branch>... --watch on the right. The parent branch comes
from the quick-start base ref or saved Scatterer branch metadata when present,
then falls back to the current GitHub PR base, then main.
Repeated invocations create another workspace/space. It does not append tabs to the workspace you invoked it from. Project config can override the Hunk command or add extra layout tabs such as a runner or git UI.
Hunk theming is best configured through Hunk itself. theme = "auto" queries the
terminal background and picks Hunk's light/dark defaults; transparent_background = true lets the terminal background show through. Hunk also has a tokyo-night
dark theme, but not a matching Tokyo Night Day theme. Herdr itself supports
name = "terminal" or dark_name = "tokyo-night" / light_name = "tokyo-night-day" in ~/.config/herdr/config.toml.
The daniel.scatterer.quick-start action opens a Herdr popup TUI. Optionally
enter a multi-line Pi prompt, choose one of three workspace modes, optionally
enter a branch name and base ref, choose a Pi model from pi --list-models, and
submit with Enter. Use Shift+Enter or Ctrl+J to add prompt lines. The
modes are:
workspace (current checkout)— opens another workspace for the current checkout, optionally switching or creating its branch firstworktree (Herdr group, indented)— keeps Herdr's normal managed worktree behavior and displays the new workspace under the source repositoryworktree (top-level space, not indented)— creates the same Git worktree in Herdr's configured worktree directory, but reopens it as an ordinary top-level workspace without Herdr grouping provenance
Use Left/Right or Space while the Mode field is active to change the behavior
for that quick-start invocation.
In workspace mode, an empty branch keeps the current branch; entering a branch
switches to it or creates it before opening the workspace. If the branch is new,
an optional base ref creates it from that ref instead of the current HEAD. In
either worktree mode, an empty branch uses daniel/<prompt-slug>, so either a
prompt or branch is required. An optional base ref is passed to Herdr when
creating the worktree; blank uses the source checkout's current ref. For stacked
PRs, enter the child PR branch in Branch and the parent PR branch in Base ref. The branch name is also used as the worktree workspace name; when Scatterer
starts Pi explicitly for a prompt/model selection, it uses the branch or current
workspace name as the Pi session name. Scatterer then:
- creates a Herdr workspace, or creates a Git worktree and opens either a grouped or top-level workspace for it
- for new worktrees only, runs project worktree setup from merged Scatterer
config,
.herdr/setup.json, and executable.herdr/setup-worktree.sh/.herdr/post-worktree-create.shhooks when present - applies the Scatterer layout in the workspace
- starts Pi through Herdr's live-agent facade, waits for it to become ready, and atomically submits the entered multiline prompt when present
For top-level mode, Scatterer lets Herdr create the checkout first so the path
still honors [worktrees].directory (for example
~/.herdr/worktrees/<repo>/<branch-slug>). It then calls workspace.close on
the temporary grouped workspace—this does not remove the checkout—and opens the
same path with workspace.create. The final workspace is a real Git worktree but
is not indented. Scatterer records these checkouts in its Herdr plugin state.
Invoke daniel.scatterer.remove-flat-worktree from inside one to remove a clean
checkout safely and close its workspace; the branch is retained. Dirty
worktrees are refused. Avoid Herdr's Open worktree action if you do not want a
top-level checkout grouped again.
Layout pane commands import direnv export bash before starting so tools like Pi,
hunk, and any project-configured runner/git commands inherit the workspace's
allowed .envrc.
If lorri has not finished evaluating yet, Scatterer waits and retries briefly
before launching panes. If direnv still fails, Scatterer continues without that
environment and disables direnv hooks in the fallback shell so the same .envrc
error does not repeat. Set [env] direnv = false in Scatterer config to disable
direnv per project.
Scatterer starts Pi in the layout's shell pane with Herdr's agent.start API,
passing --name "<branch-or-session>" and an optional --model. It then uses
agent.prompt for the initial message, avoiding shell quoting and preserving
multiline text atomically.
The daniel.scatterer.lazygit action opens lazygit in a Herdr overlay using
the focused pane's current working directory.
Scatterer includes a macOS-only temporary bridge for Pi panes while Herdr does
not yet forward host terminal light/dark color-scheme events to child panes.
Enable Herdr's own UI auto-switching in ~/.config/herdr/config.toml:
[theme]
auto_switch = true
dark_name = "tokyo-night"
light_name = "tokyo-night-day"Then run a one-shot sync, or install the LaunchAgent watcher:
scatterer appearance sync
scatterer appearance install-launchd
scatterer appearance uninstall-launchdThe watcher polls macOS AppleInterfaceStyle and sends Pi-compatible terminal
color-scheme reports (CSI ? 997 ; 1/2 n) into active Pi panes. This should be
removed once Herdr upstream can proxy child-pane color-scheme queries and
notifications (CSI ? 996 n, CSI ? 2031 h/l). Daniel should open that Herdr PR
when there is time.
The daniel.scatterer.nav-left, nav-down, nav-up, and nav-right actions
provide the Herdr side of Vim-style pane navigation. Each action checks the
focused pane's foreground process with herdr pane process-info and, for wrapper
processes such as sudo/network-namespace shells, scans descendants too:
- if it is Vim/Neovim, Scatterer sends the matching
ctrl+h/j/k/lkey into that pane so the editor can move between its own splits - if it is
ssh/mosh-client, Scatterer also sends the key into the pane so a remote Neovim/Herdr session can handle it instead of local Herdr stealing it - otherwise, Scatterer moves Herdr focus directly with
herdr pane focus
For seamless split-edge handoff, Neovim still needs a small Lua keymap that tries
wincmd h/j/k/l first and calls herdr pane focus --direction ... --current
when the current Neovim window does not change.
The daniel.scatterer.pr-picker action opens a compact Herdr popup and lists
PRs attached to active Herdr agents. It scans active agents, finds their
current worktree/branch, resolves the matching GitHub PR with gh, and shows:
- Nerd Font PR state icon: open, draft, merged, or closed
- PR number, title, review decision, CI state, comments, files, and lines changed
- selected-PR details with associated agent, agent status, branch, and URL
Controls:
↑/↓ or j/k select
Enter focus the associated workspace/agent
o open PR in browser, or copy the URL via terminal clipboard over SSH
r refresh
y copy PR URL
q/Esc closeRefreshing the picker reports pr_url, pr_number, and pr_state pane
metadata. It also reports exactly one styled-sidebar badge token for the current
state: pr_open, pr_draft, pr_merged, or pr_closed. Each badge displays
the PR number, Nerd Font state icon, and state inside the normal Herdr Agents
view; Scatterer does not replace or filter that view.
A Nix development shell supplies Rust, Clippy, rustfmt, pkg-config, and Darwin's
libiconv linkage:
cd ~/Projects/scatterer
nix develop
cargo test --locked
herdr plugin link .
herdr plugin action invoke daniel.scatterer.apply-layout
herdr plugin action invoke daniel.scatterer.quick-start
herdr plugin action invoke daniel.scatterer.pr-picker
herdr plugin action invoke daniel.scatterer.remove-flat-worktree
herdr plugin action invoke daniel.scatterer.lazygit
herdr plugin action invoke daniel.scatterer.appearance-sync
herdr plugin action invoke daniel.scatterer.appearance-install-launchd
herdr plugin action invoke daniel.scatterer.nav-leftHerdr plugins cannot self-install keybindings. Add this to your Herdr config:
[[keys.command]]
key = "prefix+shift+s"
type = "plugin_action"
command = "daniel.scatterer.apply-layout"
description = "scatterer layout"
[[keys.command]]
key = "prefix+shift+a"
type = "plugin_action"
command = "daniel.scatterer.quick-start"
description = "scatterer quick start"
[[keys.command]]
key = "prefix+shift+p"
type = "plugin_action"
command = "daniel.scatterer.pr-picker"
description = "scatterer PR picker"
[[keys.command]]
key = "prefix+shift+g"
type = "plugin_action"
command = "daniel.scatterer.lazygit"
description = "lazygit"
[[keys.command]]
key = "ctrl+h"
type = "plugin_action"
command = "daniel.scatterer.nav-left"
description = "navigate left (vim/herdr)"
[[keys.command]]
key = "ctrl+j"
type = "plugin_action"
command = "daniel.scatterer.nav-down"
description = "navigate down (vim/herdr)"
[[keys.command]]
key = "ctrl+k"
type = "plugin_action"
command = "daniel.scatterer.nav-up"
description = "navigate up (vim/herdr)"
[[keys.command]]
key = "ctrl+l"
type = "plugin_action"
command = "daniel.scatterer.nav-right"
description = "navigate right (vim/herdr)"With Daniel's current prefix = "ctrl+x", these are ctrl+x then shift+s
for layout, ctrl+x then shift+a for quick start, ctrl+x then shift+p
for PR picker, and ctrl+x then shift+g for lazygit. The navigation bindings
are direct ctrl+h/j/k/l chords, which shadow shell readline defaults such as
ctrl+l clear-screen and ctrl+k kill-line.
Scatterer merges configuration files from parent directories down to the current project directory. In each directory it loads files in this order:
scatterer.toml.scatterer.toml.scatterer.local.toml
Use .scatterer.toml for project config you are comfortable committing. Use
.scatterer.local.toml for personal machine-local overrides and add it to
.git/info/exclude or your global git ignore.
[env]
# Defaults to true. When enabled, Scatterer-created panes run
# `direnv export bash` before launching pi/hunk and any configured tabs.
direnv = true
[layout]
agent = "pi"
# Optional override; omitted default is `hunk diff <parent-branch>... --watch`.
# hunk = "hunk diff main... --watch"
# Optional per-project tabs. Defaults do not include process-compose or lazygit.
runner = "process-compose up"
git = "lazygit"
[quick_start.setup]
# Shell commands run in each new quick-start worktree before the layout is
# applied. Commands from multiple config files are appended in merge order.
commands = [
"touch .lorri-off",
"direnv allow",
]Set runner only in projects that need a runner tab, for example:
[layout]
runner = "npm run dev"Quick-start also supports setup files and executable hooks alongside config commands:
.herdr/setup.json
.herdr/setup-worktree.sh
.herdr/post-worktree-create.shScatterer checks both the source checkout and the newly-created worktree, so
local-only setup in your source checkout still runs even when the worktree has a
tracked .herdr directory. Identical tracked files are skipped to avoid running
the same setup twice.
These can be local-only too. For personal Cobb-style setup, keep the files in your checkout and ignore them locally:
cat >> .git/info/exclude <<'EOF'
.scatterer.local.toml
.herdr/setup.json
.herdr/setup-worktree.sh
.herdr/post-worktree-create.sh
EOF