aboutsummaryrefslogtreecommitdiff
path: root/dot_config/herdr
diff options
context:
space:
mode:
Diffstat (limited to 'dot_config/herdr')
-rw-r--r--dot_config/herdr/config.toml106
-rw-r--r--dot_config/herdr/empty_dot_plugins.lock0
-rw-r--r--dot_config/herdr/herdr-client.log4
-rw-r--r--dot_config/herdr/herdr-server.log670
-rw-r--r--dot_config/herdr/plugins.json122
-rw-r--r--dot_config/herdr/plugins/config/herdr-splits/herdr-splits.conf15
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/CONTRIBUTING.md126
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/LICENSE21
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/Makefile21
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/README.md445
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/deps/mini.test/.keep0
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/FETCH_HEAD1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/HEAD1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/config10
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/description1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_applypatch-msg.sample15
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_commit-msg.sample24
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_fsmonitor-watchman.sample174
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_post-update.sample8
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-applypatch.sample14
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-commit.sample49
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-merge-commit.sample13
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-push.sample53
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-rebase.sample169
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-receive.sample24
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_prepare-commit-msg.sample42
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_push-to-checkout.sample78
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_sendemail-validate.sample77
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_update.sample128
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/indexbin0 -> 3078 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/info/exclude6
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/logs/HEAD1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/00/readonly_5337796b0a74934b058105cc428c5616fd0f0dbin0 -> 2321 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/0a/readonly_fc9295e33d59a441e3be5e0e642938ffbda0e0bin0 -> 574 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/10/readonly_7273e004e4f7ef07f13c83164d2cb2c51df65dbin0 -> 188 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/16/readonly_d0b0db945ddf3d303278b53db08991f21ee859bin0 -> 1118 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/17/readonly_63803d3dbf5f7131e57db9486568c5fb744450bin0 -> 2771 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_4f4a2cc4bd8534e7df9f92f3fe62c91693463ebin0 -> 52 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_52c3623b72f8cad4b4f68f961bc9a12df4fe841
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_51026d393d97f55d261e1e20dbce0bb707b784bin0 -> 678 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_8209034800d64adf3487b3819bd6a531ae0606bin0 -> 1305 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3c/readonly_dd672a9627ea5b0f567efa2a860492622b1415bin0 -> 2252 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3f/readonly_e8254ac9e3b167cd84e4c427d87f8af76e23ea1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/41/readonly_399a89bfca1a0095f7a53d1f89810861242e79bin0 -> 97 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/47/readonly_f6821b845d7069c2c68f7c7082be8e2c152b04bin0 -> 1539 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/4d/readonly_32363ad9bc23df7acbbd83598565e1401686822
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/51/readonly_1d507c6ba403c265e84aa3d6b5a66aef442953bin0 -> 650 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/52/readonly_f002f346b87b770ebb3747d801684ff4d5f56abin0 -> 1980 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/56/readonly_ecca37040cae81738e7dc8c44e8e705d7f744cbin0 -> 2470 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/6f/readonly_ecb9315d55b29f0f5f1a0efcc21b4ee10e6507bin0 -> 394 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7b/readonly_ecca042405c772eac6ec40f362327ad8e36693bin0 -> 282 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7f/readonly_9cd53e889a0af1c8ac06d90a7f529692fb7082bin0 -> 7582 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8e/readonly_9f3e285bae904c5dad085e888395b10ceeab26bin0 -> 984 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8f/readonly_bc9e7a54d4cd045744e453334f71ee63780c03bin0 -> 95 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/92/readonly_188bae7a779d87d71fb99f7953ed0bda60c6ebbin0 -> 132 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/99/readonly_2c6604f456443ea726645083a1e33ab3ba3abcbin0 -> 542 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/9c/readonly_dbbb7b9d0d61f7384a4d05f12dc7e2075fba7cbin0 -> 2049 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a3/readonly_e4fc94c4ef9904a9c3dc43a7692439c8692023bin0 -> 54 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a5/readonly_485ad8e3a1602c46b2e9811abd0452761c1e70bin0 -> 1553 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ae/readonly_cd3e093eccea0460991fdc918bdda0b58c5c11bin0 -> 1891 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/c8/readonly_312fd11e35846f0f2f2fb994b40fe1dc4cf948bin0 -> 1248 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ca/readonly_d3716f06cd3b80418b5bd06b7ddcf844502a58bin0 -> 60 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d1/readonly_7b920f681623037f9c4d83442aae19716c0744bin0 -> 4244 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d4/readonly_09b5ac3451619c127ef249a2d75e8e4f0693bebin0 -> 433 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d8/readonly_a3be96e59ca11039dd2bd5c482613e90fc0c762
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_c8ddd059a98b318cf0225cff3bb67ab6d66ac1bin0 -> 136 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_ef3949f5d970786e78d88f1eb2687e68d3d476bin0 -> 1606 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/df/readonly_6d5aead0ff3e1663dd2e5e9f179ba88e886dc0bin0 -> 490 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/e2/readonly_a3be9442717a2d5028222e5310175c26bf7eb3bin0 -> 206 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f3/readonly_5b8b893f1aa6d80da27731c2a685771d9a614fbin0 -> 31 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f8/readonly_8a80ddfe620609eed45752ddf4cf4cb3cd3ffdbin0 -> 3829 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/fd/readonly_e44002117933a436ae94729bd1133236ab9f6cbin0 -> 51 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ff/readonly_94999bc31a7ddc26674e3dbda98f68e0c133b6bin0 -> 1491 bytes
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/info/.keep0
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/pack/.keep0
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/heads/.keep0
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/tags/.keep0
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/shallow1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/bug_report.yml100
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/feature_request.yml32
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/PULL_REQUEST_TEMPLATE.md31
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/workflows/ci.yml43
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitignore1
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitmodules3
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/herdr-plugin.toml62
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/conf.lua236
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/config.lua146
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/health.lua46
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/herdr.lua160
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/init.lua162
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua206
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/resize.lua108
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/sync.lua107
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/win.lua154
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/plugin/herdr-splits.lua11
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-nav.sh104
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-resize.sh58
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/minimal_init.lua14
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_conf.lua196
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_herdr.lua267
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua520
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_plugin.lua60
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_resize.lua302
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_win.lua138
-rw-r--r--dot_config/herdr/session.json40
105 files changed, 5732 insertions, 0 deletions
diff --git a/dot_config/herdr/config.toml b/dot_config/herdr/config.toml
new file mode 100644
index 0000000..3a5e277
--- /dev/null
+++ b/dot_config/herdr/config.toml
@@ -0,0 +1,106 @@
+onboarding = false
+
+[terminal]
+# New panes inherit the focused pane's working directory.
+new_cwd = "follow"
+
+[ui]
+# Keep the project/workspace sidebar visible and useful.
+sidebar_width = 28
+sidebar_min_width = 20
+sidebar_max_width = 38
+
+# Visually distinguish Herdr panes without wasting much space.
+pane_borders = true
+pane_gaps = false
+
+# Hide the tab bar when a workspace only has one tab.
+hide_tab_bar_when_single_tab = true
+
+# Mouse support is useful on the Mac, particularly while learning Herdr.
+mouse_capture = true
+copy_on_select = true
+
+[keys]
+prefix = "ctrl+space"
+
+# Project switching.
+workspace_picker = "prefix+w"
+goto = "prefix+g"
+switch_workspace = "prefix+shift+1..9"
+previous_workspace = "prefix+shift+["
+next_workspace = "prefix+shift+]"
+
+# Quickly return to the previously focused pane, even across workspaces.
+last_pane = "prefix+backtick"
+
+# Tabs inside the current project.
+new_tab = "prefix+c"
+previous_tab = "prefix+p"
+next_tab = "prefix+n"
+switch_tab = "prefix+1..9"
+
+# Pane operations.
+split_vertical = "prefix+/"
+split_horizontal = "prefix+minus"
+zoom = "prefix+z"
+close_pane = "prefix+x"
+
+# Keep prefixed navigation as an escape hatch.
+focus_pane_left = "prefix+h"
+focus_pane_down = "prefix+j"
+focus_pane_up = "prefix+k"
+focus_pane_right = "prefix+l"
+
+# Direct Ctrl+HJKL is handled by herdr-splits below.
+
+[[keys.command]]
+key = "ctrl+h"
+type = "plugin_action"
+command = "herdr-splits.nav-left"
+description = "navigate left through Neovim and Herdr"
+
+[[keys.command]]
+key = "ctrl+j"
+type = "plugin_action"
+command = "herdr-splits.nav-down"
+description = "navigate down through Neovim and Herdr"
+
+[[keys.command]]
+key = "ctrl+k"
+type = "plugin_action"
+command = "herdr-splits.nav-up"
+description = "navigate up through Neovim and Herdr"
+
+[[keys.command]]
+key = "ctrl+l"
+type = "plugin_action"
+command = "herdr-splits.nav-right"
+description = "navigate right through Neovim and Herdr"
+
+[[keys.command]]
+key = "alt+h"
+type = "plugin_action"
+command = "herdr-splits.resize-left"
+description = "resize left"
+
+[[keys.command]]
+key = "alt+j"
+type = "plugin_action"
+command = "herdr-splits.resize-down"
+description = "resize down"
+
+[[keys.command]]
+key = "alt+k"
+type = "plugin_action"
+command = "herdr-splits.resize-up"
+description = "resize up"
+
+[[keys.command]]
+key = "alt+l"
+type = "plugin_action"
+command = "herdr-splits.resize-right"
+description = "resize right"
+
+[ui.toast]
+delivery = "herdr"
diff --git a/dot_config/herdr/empty_dot_plugins.lock b/dot_config/herdr/empty_dot_plugins.lock
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dot_config/herdr/empty_dot_plugins.lock
diff --git a/dot_config/herdr/herdr-client.log b/dot_config/herdr/herdr-client.log
new file mode 100644
index 0000000..f9036a6
--- /dev/null
+++ b/dot_config/herdr/herdr-client.log
@@ -0,0 +1,4 @@
+2026-07-24T20:15:47.544152Z INFO herdr::logging: herdr starting event="app.startup" subsystem="client" outcome="started" pid=8768
+2026-07-24T20:15:47.544192Z INFO herdr::client: connecting to server path=/Users/zamp/.config/herdr/herdr-client.sock
+2026-07-24T20:15:47.774089Z INFO herdr::client: handshake succeeded version=17 encoding=SemanticFrame
+2026-07-27T08:17:29.705493Z INFO herdr::logging: herdr exiting event="app.shutdown" subsystem="client" outcome="completed" pid=8768
diff --git a/dot_config/herdr/herdr-server.log b/dot_config/herdr/herdr-server.log
new file mode 100644
index 0000000..1588c09
--- /dev/null
+++ b/dot_config/herdr/herdr-server.log
@@ -0,0 +1,670 @@
+2026-07-24T20:15:47.495927Z INFO herdr::platform::macos: raised server file descriptor soft limit previous=256 target=8192
+2026-07-24T20:15:47.496202Z INFO herdr::api::server: api server listening path=/Users/zamp/.config/herdr/herdr.sock
+2026-07-24T20:15:47.496722Z INFO herdr::app: using pane scrollback configuration pane_scrollback_limit_bytes=10000000
+2026-07-24T20:15:47.510661Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-24T20:15:47.515100Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=1 rows=24 cols=80 scrollback_limit_bytes=10000000
+2026-07-24T20:15:47.517632Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=1 pid=8794
+2026-07-24T20:15:47.517974Z INFO herdr::logging: workspace created event="workspace.create" subsystem="workspace" outcome="ok" workspace_id="w1" pane_id=1
+2026-07-24T20:15:47.517985Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w1"
+2026-07-24T20:15:47.518010Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:15:47.518044Z INFO herdr::server::headless: created startup workspace cwd=/Users/zamp
+2026-07-24T20:15:47.518254Z INFO herdr::server::headless: client protocol socket listening path=/Users/zamp/.config/herdr/herdr-client.sock
+2026-07-24T20:15:47.518289Z INFO herdr::server::headless: herdr server started api_socket=/Users/zamp/.config/herdr/herdr.sock client_socket=/Users/zamp/.config/herdr/herdr-client.sock
+2026-07-24T20:15:47.518602Z INFO herdr::logging: herdr starting event="app.startup" subsystem="server" outcome="started" pid=8769
+2026-07-24T20:15:47.774119Z INFO herdr::server::headless: client connected client_id=2 cols=216 rows=61 cell_width_px=0 cell_height_px=0 render_encoding=SemanticFrame
+2026-07-24T20:15:52.523465Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:16:19.387859Z INFO herdr::pane: agent changed pane=1 previous_agent=None agent=Some(Codex) process=codex pgid=Some(9125)
+2026-07-24T20:16:24.558399Z INFO herdr::pane: agent changed pane=1 previous_agent=Some(Codex) agent=None pgid=Some(8794)
+2026-07-24T20:16:53.702239Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=2 rows=60 cols=190 scrollback_limit_bytes=10000000
+2026-07-24T20:16:53.707432Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=2 pid=9204
+2026-07-24T20:16:53.707741Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:16:58.718080Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:17:00.098164Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=2 status="ExitStatus { code: 0, signal: None }"
+2026-07-24T20:17:00.098404Z INFO herdr::pane: pane session terminated pane=2 pid=9204 signal=Hangup
+2026-07-24T20:17:05.103386Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:19:32.845531Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=3 rows=60 cols=190 scrollback_limit_bytes=10000000
+2026-07-24T20:19:32.853251Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=3 pid=9357
+2026-07-24T20:19:32.853741Z INFO herdr::logging: workspace created event="workspace.create" subsystem="workspace" outcome="ok" workspace_id="w2" pane_id=3
+2026-07-24T20:19:32.853805Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w2"
+2026-07-24T20:19:32.853986Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w2" tab_id="w2:t1"
+2026-07-24T20:19:35.669009Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w1"
+2026-07-24T20:19:35.669587Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:19:36.322288Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w2"
+2026-07-24T20:19:36.322643Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w2" tab_id="w2:t1"
+2026-07-24T20:19:36.894143Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w1"
+2026-07-24T20:19:36.894634Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:19:40.710704Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w2"
+2026-07-24T20:19:40.711158Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w2" tab_id="w2:t1"
+2026-07-24T20:19:40.885895Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w2"
+2026-07-24T20:19:40.886509Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w2" tab_id="w2:t1"
+2026-07-24T20:19:41.652479Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w2"
+2026-07-24T20:19:41.652930Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w2" tab_id="w2:t1"
+2026-07-24T20:19:44.824051Z INFO herdr::logging: workspace closed event="workspace.close" subsystem="workspace" outcome="ok" workspace_id="w2"
+2026-07-24T20:19:44.825788Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=3 status="ExitStatus { code: 1, signal: None }"
+2026-07-24T20:19:44.846478Z INFO herdr::pane: pane session terminated pane=3 pid=9357 signal=Hangup
+2026-07-24T20:19:44.846780Z WARN herdr::app::actions: PaneDied for unknown pane pane=3
+2026-07-24T20:19:49.853275Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:23:03.120646Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:server:reload-config" method="server.reload_config" changes_ui=true
+2026-07-24T20:23:03.121645Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:server:reload-config" method="server.reload_config"
+2026-07-24T20:27:56.051742Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=4 rows=61 cols=188 scrollback_limit_bytes=10000000
+2026-07-24T20:27:56.059988Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=4 pid=10184
+2026-07-24T20:27:56.060708Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:01.068501Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:28:03.484689Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:03.484817Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:03.484853Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:03.484898Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:03.854267Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:03.854376Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:03.854411Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:03.854440Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:03.988757Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:03.988858Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:03.988891Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:03.988918Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.140185Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.140300Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.140337Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.140365Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.219212Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.219322Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.219353Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.219384Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.349179Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.349296Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.349332Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.349361Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.396051Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.396151Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.396179Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.396227Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.512651Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.512742Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.512771Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.512801Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.595812Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.595897Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.595931Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.595960Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:04.736016Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:04.736118Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.736151Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:04.736180Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:05.059772Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:05.059874Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:05.059910Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:05.059937Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:05.329296Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:05.329399Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:05.329433Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:05.329460Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:05.572759Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:05.572861Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:05.572894Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:05.572932Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:06.126137Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:06.126241Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:06.126275Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:06.126302Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:06.367841Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:06.367947Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:06.367981Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:06.368007Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:08.880422Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=5 rows=61 cols=94 scrollback_limit_bytes=10000000
+2026-07-24T20:28:08.888439Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=5 pid=10510
+2026-07-24T20:28:08.889393Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t2"
+2026-07-24T20:28:10.493117Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:11.439894Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t2"
+2026-07-24T20:28:12.083552Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:14.070228Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t2"
+2026-07-24T20:28:14.842639Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:16.199704Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:16.199808Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:16.199844Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:16.199875Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:18.846218Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:18.846633Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:18.846675Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:18.846704Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:19.555043Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:19.555154Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:19.555190Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:19.555220Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:19.887077Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:19.887175Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:19.887211Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:19.887240Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:24.892830Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:28:43.487460Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:43.487559Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:43.487593Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:43.487620Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:44.050842Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:44.050945Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.050980Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.051008Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:44.367429Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:44.367515Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.367549Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.367577Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:44.567157Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:44.567267Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.567301Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.567332Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:44.755395Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:44.755487Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.755520Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.755553Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:44.883669Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:44.883761Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.883794Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:44.883821Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:45.109183Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:45.109278Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:45.109314Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:45.109345Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:45.358467Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:45.358568Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:45.358602Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:45.358629Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:47.622638Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=6 rows=60 cols=94 scrollback_limit_bytes=10000000
+2026-07-24T20:28:47.627635Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=6 pid=11042
+2026-07-24T20:28:47.627926Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:51.147656Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=6 status="ExitStatus { code: 1, signal: None }"
+2026-07-24T20:28:51.167453Z INFO herdr::pane: pane session terminated pane=6 pid=11042 signal=Hangup
+2026-07-24T20:28:51.168762Z WARN herdr::app::actions: PaneDied for unknown pane pane=6
+2026-07-24T20:28:52.245707Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:28:52.245804Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:52.245839Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:28:52.245886Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:28:57.255281Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:29:01.690454Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:29:01.690556Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:29:01.690591Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:29:01.690617Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:29:02.356107Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=4 status="ExitStatus { code: 1, signal: None }"
+2026-07-24T20:29:02.378950Z INFO herdr::pane: pane session terminated pane=4 pid=10184 signal=Hangup
+2026-07-24T20:29:02.380356Z WARN herdr::app::actions: PaneDied for unknown pane pane=4
+2026-07-24T20:29:07.384503Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:29:08.117360Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w1"
+2026-07-24T20:29:08.117845Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:29:08.118208Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t2"
+2026-07-24T20:29:08.121378Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=5 status="ExitStatus { code: 1, signal: None }"
+2026-07-24T20:29:08.140690Z INFO herdr::pane: pane session terminated pane=5 pid=10510 signal=Hangup
+2026-07-24T20:29:08.142026Z WARN herdr::app::actions: PaneDied for unknown pane pane=5
+2026-07-24T20:29:13.144294Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:29:14.368664Z INFO herdr::server::headless: client resize client_id=2 cols=216 rows=63 cell_width_px=0 cell_height_px=0
+2026-07-24T20:29:55.026465Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=7 rows=63 cols=188 scrollback_limit_bytes=10000000
+2026-07-24T20:29:55.034370Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=7 pid=11194
+2026-07-24T20:29:55.035200Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:30:00.260708Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=7 status="ExitStatus { code: 1, signal: None }"
+2026-07-24T20:30:00.283775Z INFO herdr::pane: pane session terminated pane=7 pid=11194 signal=Hangup
+2026-07-24T20:30:00.285320Z WARN herdr::app::actions: PaneDied for unknown pane pane=7
+2026-07-24T20:30:05.292500Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:37:31.320771Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=8 rows=63 cols=188 scrollback_limit_bytes=10000000
+2026-07-24T20:37:31.323406Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=8 pid=14997
+2026-07-24T20:37:31.323602Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:37:36.330791Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:39:29.429357Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:39:29.429466Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:39:29.429500Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:39:29.429532Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:39:34.437047Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:39:49.443605Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:39:49.443713Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:39:49.443751Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:39:49.443780Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:39:50.986759Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=8 status="ExitStatus { code: 1, signal: None }"
+2026-07-24T20:39:51.009203Z INFO herdr::pane: pane session terminated pane=8 pid=14997 signal=Hangup
+2026-07-24T20:39:51.010458Z WARN herdr::app::actions: PaneDied for unknown pane pane=8
+2026-07-24T20:39:56.013104Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:45:47.507377Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-24T20:48:10.440858Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=9 rows=63 cols=188 scrollback_limit_bytes=10000000
+2026-07-24T20:48:10.448173Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=9 pid=18572
+2026-07-24T20:48:10.448699Z INFO herdr::logging: tab renamed event="tab.rename" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T20:48:10.448952Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T20:48:15.460482Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:48:16.971538Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:48:21.981885Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:48:25.785934Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=10 rows=62 cols=188 scrollback_limit_bytes=10000000
+2026-07-24T20:48:25.794666Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=10 pid=18695
+2026-07-24T20:48:25.795478Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:48:27.872492Z INFO herdr::pane: agent changed pane=10 previous_agent=None agent=Some(Codex) process=codex pgid=Some(18777)
+2026-07-24T20:48:30.805081Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:48:45.389622Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:48:45.389730Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:48:45.389768Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:48:45.389796Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:48:50.398066Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:49:29.975339Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:49:29.975447Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:49:29.975483Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:49:29.975513Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:49:34.981613Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:51:22.915523Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:51:22.915626Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:22.915659Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:22.915691Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:51:23.760451Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:51:23.760555Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:23.760592Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:23.760621Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:51:24.474175Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:51:24.474303Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:24.474344Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:24.474376Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:51:24.979965Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:51:24.980079Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:24.980112Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:51:24.980139Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:51:29.985734Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:57:01.048251Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:57:06.060548Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:57:46.869917Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T20:57:46.870032Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:57:46.870089Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:57:46.870122Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T20:57:51.877083Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T20:57:53.710265Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T20:57:58.719990Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:00:26.212153Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:02:39.786251Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:06:20.511195Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:06:21.779877Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:06:26.789479Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:06:49.204781Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:06:54.213071Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:07:02.222091Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:07:07.230383Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:07:18.660887Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:07:22.499473Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:07:27.332116Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=11 rows=62 cols=212 scrollback_limit_bytes=10000000
+2026-07-24T21:07:27.339837Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=11 pid=20255
+2026-07-24T21:07:27.340448Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:07:32.347272Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:07:43.146396Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:07:44.572325Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:07:44.572390Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:07:44.572399Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:07:44.572426Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:07:49.580441Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:08:14.437071Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:08:15.777810Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:08:15.777886Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:08:15.777902Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:08:15.777937Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:08:20.781601Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:11:07.825989Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:11:11.140914Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:11:11.140989Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:11:11.141005Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:11:11.141033Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:11:16.146935Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:12:09.439364Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:12:14.443736Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:12:18.452091Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:12:20.209770Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:12:25.220470Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:14:02.865464Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:14:07.873823Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:14:37.745692Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:14:37.745757Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:14:37.745767Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:14:37.745797Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:14:42.751030Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:15:47.514610Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-24T21:19:07.660869Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:19:12.668963Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:26:03.469834Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:26:08.480447Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:27:50.532495Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:27:55.544999Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:32:14.768807Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:32:14.768872Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:32:14.768887Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:32:14.768917Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:32:19.777694Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:33:04.426832Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:33:09.439697Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:36:01.598714Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T21:36:06.613697Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:38:49.966643Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:38:51.093872Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:38:51.093930Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:38:51.093942Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:38:51.093969Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:38:56.103494Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:41:27.064655Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:resize" method="pane.resize" changes_ui=true
+2026-07-24T21:41:27.064757Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:resize" method="pane.resize"
+2026-07-24T21:41:28.837157Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:resize" method="pane.resize" changes_ui=true
+2026-07-24T21:41:28.837256Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:resize" method="pane.resize"
+2026-07-24T21:41:29.581641Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:41:29.581693Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:41:29.581705Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:41:29.581730Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:41:34.587134Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:43:30.874776Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:43:35.889111Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:45:47.516638Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-24T21:45:54.626666Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:45:54.626748Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:45:54.626762Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:45:54.626793Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:45:59.635321Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:49:08.994570Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:49:08.994650Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:49:08.994679Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:49:08.994712Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:49:14.002985Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:52:13.003183Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:52:13.003242Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:52:13.003253Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:52:13.003277Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:52:18.012305Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:53:26.751038Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:53:26.751109Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:53:26.751124Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:53:26.751152Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:53:31.757250Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T21:57:46.030420Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T21:57:46.030493Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:57:46.030507Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T21:57:46.030537Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T21:57:51.039160Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:01:40.805870Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:01:45.817930Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:01:54.710020Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T22:01:54.710073Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:01:54.710082Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:01:54.710105Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T22:01:59.529421Z INFO herdr::app::actions: copied selection to clipboard
+2026-07-24T22:01:59.720718Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:02:00.407938Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:02:05.417311Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:04:30.290820Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:04:35.301373Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:05:06.520174Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T22:05:06.520243Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:05:06.520256Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:05:06.520284Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T22:05:11.530318Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:05:19.850836Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T22:05:24.858198Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:05:32.018640Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:05:37.029531Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:05:48.426692Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T22:05:53.436963Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:06:01.164550Z INFO herdr::app::actions: copied selection to clipboard
+2026-07-24T22:06:02.103445Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:06:03.184718Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:06:08.197923Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:07:10.241276Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T22:07:10.241340Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:07:10.241349Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:07:10.241380Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T22:07:15.244260Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:07:33.896022Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-24T22:07:36.790544Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:07:41.800511Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-24T22:09:20.154594Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-24T22:09:20.154659Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:09:20.154669Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-24T22:09:20.154699Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-24T22:09:25.163183Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T03:20:52.664554Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T08:33:40.425668Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:33:40.425733Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:33:40.425746Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:33:40.425779Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:33:45.439002Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:34:14.220647Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:34:14.221769Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:34:14.221794Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:34:14.221831Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:34:18.468246Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:34:18.468321Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:34:18.468338Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:34:18.468368Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:34:23.476684Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:35:23.015877Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:35:28.032512Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:37:58.545558Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:38:02.481869Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:38:02.482722Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:38:02.482738Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:38:02.482770Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:38:07.492669Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:38:10.772436Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:38:15.780131Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:41:53.205279Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:41:58.222280Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:42:00.144382Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:42:00.144449Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:42:00.144458Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:42:00.144487Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:42:05.151595Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:47:43.263886Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=12 rows=62 cols=106 scrollback_limit_bytes=10000000
+2026-07-25T08:47:43.269625Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=12 pid=28595
+2026-07-25T08:47:43.269860Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:47:48.277627Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:47:50.558718Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:47:50.558780Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:47:50.558791Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:47:50.558825Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:47:55.564254Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:48:08.070987Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:48:08.071053Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:08.071065Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:08.071093Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:48:09.887251Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:48:09.887303Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:09.887313Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:09.887345Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:48:14.891509Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:48:23.083718Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:48:23.083794Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:23.083809Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:23.083840Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:48:28.089241Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:48:39.175711Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:44.186460Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:48:47.336661Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:52.346317Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:48:59.110141Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:48:59.110210Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:59.110224Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:48:59.110254Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:49:04.117759Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:49:19.315625Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:49:19.315717Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:49:19.315735Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:49:19.315771Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:49:24.322972Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:56:00.518594Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:56:00.518693Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:56:00.518713Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:56:00.518746Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:56:05.523856Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T08:56:13.147947Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T08:57:22.557892Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:57:22.557968Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:57:22.557982Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:57:22.558016Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:57:23.381280Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T08:57:23.381337Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:57:23.381347Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T08:57:23.381377Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T08:57:28.384413Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:01:14.040058Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=13 rows=31 cols=106 scrollback_limit_bytes=10000000
+2026-07-25T09:01:14.049736Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=13 pid=29177
+2026-07-25T09:01:14.050307Z INFO herdr::logging: tab renamed event="tab.rename" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:01:14.050404Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:01:17.569986Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:01:21.332959Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=12 status="ExitStatus { code: 1, signal: None }"
+2026-07-25T09:01:21.352047Z INFO herdr::pane: pane session terminated pane=12 pid=28595 signal=Hangup
+2026-07-25T09:01:21.353403Z WARN herdr::app::actions: PaneDied for unknown pane pane=12
+2026-07-25T09:01:22.456339Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:01:27.464643Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:04:38.947773Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:04:43.958494Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:04:44.920527Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-25T09:04:46.791110Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:04:49.847732Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:04:51.464113Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:04:56.474945Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:05:05.746218Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:05:07.638231Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:05:12.645021Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:06:29.197143Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=14 rows=62 cols=212 scrollback_limit_bytes=10000000
+2026-07-25T09:06:29.204615Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=14 pid=29271
+2026-07-25T09:06:29.205045Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:06:34.210446Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:06:37.525010Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:06:37.525078Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:06:37.525096Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:06:37.525125Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:06:42.533462Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:06:55.876025Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:06:55.876117Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:06:55.876133Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:06:55.876168Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:07:00.881839Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:07:22.906891Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:07:22.906967Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:07:22.906983Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:07:22.907020Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:07:27.910421Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:07:56.330518Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:07:56.330585Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:07:56.330596Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:07:56.330627Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:08:01.340699Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:08:02.853921Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:08:02.853990Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:08:02.854001Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:08:02.854030Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:08:07.862980Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:15:47.482698Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-25T09:15:52.490243Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:17:15.436479Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:17:19.487520Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:17:19.487587Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:17:19.487596Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:17:19.487626Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:17:24.495824Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:19:07.888874Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:19:09.664094Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:19:13.805505Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:19:15.805554Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:19:20.814809Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:19:37.170473Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:19:37.170556Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:19:37.170571Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:19:37.170603Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:19:42.177066Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:22:31.663169Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t3"
+2026-07-25T09:22:36.672066Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:23:33.068860Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:23:38.081380Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:26:13.183920Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T09:27:18.062214Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:27:23.072259Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:28:02.470711Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:28:07.480177Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:30:03.106417Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:30:03.106487Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:30:03.106502Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:30:03.106528Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:30:05.528427Z INFO herdr::pane: agent changed pane=14 previous_agent=None agent=Some(Codex) process=codex pgid=Some(30779)
+2026-07-25T09:30:08.110643Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:30:11.304221Z INFO herdr::pane: agent changed pane=14 previous_agent=Some(Codex) agent=None pgid=Some(29271)
+2026-07-25T09:30:15.845291Z INFO herdr::pane: agent changed pane=14 previous_agent=None agent=Some(Codex) process=codex pgid=Some(30989)
+2026-07-25T09:31:36.914954Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:31:41.919085Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T09:32:23.005800Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-25T09:32:23.005867Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:32:23.005876Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t4"
+2026-07-25T09:32:23.005908Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-25T09:32:24.769763Z INFO herdr::pane: agent changed pane=14 previous_agent=Some(Codex) agent=None pgid=Some(29271)
+2026-07-25T09:32:25.294189Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=14 status="ExitStatus { code: 1, signal: None }"
+2026-07-25T09:32:25.314788Z INFO herdr::pane: pane session terminated pane=14 pid=29271 signal=Hangup
+2026-07-25T09:32:25.316478Z WARN herdr::app::actions: PaneDied for unknown pane pane=14
+2026-07-25T09:32:26.951276Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-25T09:32:31.961966Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-25T10:13:30.232263Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T11:16:23.997646Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T11:46:24.000054Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T12:16:24.002870Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-25T21:27:21.050030Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-26T05:41:02.085859Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-26T21:23:23.272165Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T08:17:12.366384Z INFO herdr::logging: api request received event="api.request.start" subsystem="api" outcome="started" request_id="cli:pane:focus" method="pane.focus_direction" changes_ui=true
+2026-07-27T08:17:12.366679Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-27T08:17:12.366693Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w1" tab_id="w1:t1"
+2026-07-27T08:17:12.366729Z INFO herdr::logging: api request completed event="api.request.complete" subsystem="api" outcome="ok" request_id="cli:pane:focus" method="pane.focus_direction"
+2026-07-27T08:17:13.176887Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=10 status="ExitStatus { code: 1, signal: None }"
+2026-07-27T08:17:13.195909Z INFO herdr::pane: pane session terminated pane=10 pid=18695 signal=Hangup
+2026-07-27T08:17:13.196693Z WARN herdr::app::actions: PaneDied for unknown pane pane=10
+2026-07-27T08:17:14.600139Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=1 status="ExitStatus { code: 1, signal: None }"
+2026-07-27T08:17:14.620176Z INFO herdr::pane: pane session terminated pane=1 pid=8794 signal=Hangup
+2026-07-27T08:17:14.621568Z WARN herdr::app::actions: PaneDied for unknown pane pane=1
+2026-07-27T08:17:15.556356Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=9 status="ExitStatus { code: 1, signal: None }"
+2026-07-27T08:17:15.577929Z INFO herdr::pane: pane session terminated pane=9 pid=18572 signal=Hangup
+2026-07-27T08:17:15.579241Z WARN herdr::app::actions: PaneDied for unknown pane pane=9
+2026-07-27T08:17:16.216807Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=11 status="ExitStatus { code: 1, signal: None }"
+2026-07-27T08:17:16.238969Z INFO herdr::pane: pane session terminated pane=11 pid=20255 signal=Hangup
+2026-07-27T08:17:16.240401Z WARN herdr::app::actions: PaneDied for unknown pane pane=11
+2026-07-27T08:17:16.875575Z INFO herdr::logging: workspace closed event="workspace.close" subsystem="workspace" outcome="ok" workspace_id="w1"
+2026-07-27T08:17:16.879383Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=13 status="ExitStatus { code: 1, signal: None }"
+2026-07-27T08:17:16.898617Z INFO herdr::pane: pane session terminated pane=13 pid=29177 signal=Hangup
+2026-07-27T08:17:16.900560Z WARN herdr::app::actions: PaneDied for unknown pane pane=13
+2026-07-27T08:17:16.900799Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=15 rows=63 cols=212 scrollback_limit_bytes=10000000
+2026-07-27T08:17:16.910328Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=15 pid=37908
+2026-07-27T08:17:16.910899Z INFO herdr::logging: workspace created event="workspace.create" subsystem="workspace" outcome="ok" workspace_id="w3" pane_id=15
+2026-07-27T08:17:16.910964Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w3"
+2026-07-27T08:17:16.910994Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w3" tab_id="w3:t1"
+2026-07-27T08:17:19.902825Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=15 status="ExitStatus { code: 0, signal: None }"
+2026-07-27T08:17:19.903131Z INFO herdr::pane: pane session terminated pane=15 pid=37908 signal=Hangup
+2026-07-27T08:17:19.903230Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=16 rows=63 cols=212 scrollback_limit_bytes=10000000
+2026-07-27T08:17:19.906898Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=16 pid=37972
+2026-07-27T08:17:19.907051Z INFO herdr::logging: workspace created event="workspace.create" subsystem="workspace" outcome="ok" workspace_id="w4" pane_id=16
+2026-07-27T08:17:19.907058Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w4"
+2026-07-27T08:17:19.907064Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w4" tab_id="w4:t1"
+2026-07-27T08:17:21.725815Z INFO herdr::logging: pane child exited event="pane.exit" subsystem="pane" outcome="completed" pane_id=16 status="ExitStatus { code: 0, signal: None }"
+2026-07-27T08:17:21.726361Z INFO herdr::pane: pane session terminated pane=16 pid=37972 signal=Hangup
+2026-07-27T08:17:21.726515Z INFO herdr::logging: spawning pane terminal event="pane.spawn.start" subsystem="pane" outcome="started" pane_id=17 rows=63 cols=212 scrollback_limit_bytes=10000000
+2026-07-27T08:17:21.732198Z INFO herdr::logging: pane child spawned event="pane.spawned" subsystem="pane" outcome="ok" pane_id=17 pid=38019
+2026-07-27T08:17:21.732424Z INFO herdr::logging: workspace created event="workspace.create" subsystem="workspace" outcome="ok" workspace_id="w5" pane_id=17
+2026-07-27T08:17:21.732435Z INFO herdr::logging: workspace focused event="workspace.focus" subsystem="workspace" outcome="ok" workspace_id="w5"
+2026-07-27T08:17:21.732443Z INFO herdr::logging: tab focused event="tab.focus" subsystem="tab" outcome="ok" workspace_id="w5" tab_id="w5:t1"
+2026-07-27T08:17:26.742162Z INFO herdr::logging: session saved event="persist.save" subsystem="persist" outcome="ok" path=/Users/zamp/.config/herdr/session.json workspaces=1
+2026-07-27T08:17:29.704424Z INFO herdr::server::headless: client detach requested via keybind client_id=2
+2026-07-27T08:17:29.709419Z INFO herdr::server::headless: client disconnected client_id=2
+2026-07-27T08:22:06.703297Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T09:32:36.426244Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T10:56:16.883118Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T11:26:16.857274Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T11:56:16.872766Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T12:26:16.877180Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-27T22:00:33.686857Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T07:19:40.075642Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T07:49:40.084916Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T08:19:40.096188Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T08:49:40.089294Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T09:19:40.067308Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T09:49:40.090768Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T10:54:13.991411Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T11:32:26.492804Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T12:02:26.482344Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T12:32:26.474783Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T13:02:26.488631Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T13:32:26.464326Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T14:02:26.496166Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T15:54:34.086319Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T18:11:57.017781Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T18:44:07.003930Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T19:14:06.972607Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T19:44:06.959635Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T20:14:06.999784Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T20:44:07.011166Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
+2026-07-28T21:34:00.934106Z INFO herdr::logging: checking for updates event="update.check.start" subsystem="update" outcome="started"
diff --git a/dot_config/herdr/plugins.json b/dot_config/herdr/plugins.json
new file mode 100644
index 0000000..31d5f40
--- /dev/null
+++ b/dot_config/herdr/plugins.json
@@ -0,0 +1,122 @@
+[
+ {
+ "plugin_id": "herdr-splits",
+ "name": "Herdr Splits",
+ "version": "0.5.1",
+ "min_herdr_version": "0.7.0",
+ "description": "Seamless navigation and resizing between Herdr panes and Neovim splits",
+ "manifest_path": "/Users/zamp/.config/herdr/plugins/github/herdr-splits-d880c4924f2b/herdr-plugin.toml",
+ "plugin_root": "/Users/zamp/.config/herdr/plugins/github/herdr-splits-d880c4924f2b",
+ "enabled": true,
+ "platforms": [
+ "linux",
+ "macos"
+ ],
+ "actions": [
+ {
+ "id": "nav-down",
+ "title": "Navigate down (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-nav.sh",
+ "down"
+ ]
+ },
+ {
+ "id": "nav-left",
+ "title": "Navigate left (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-nav.sh",
+ "left"
+ ]
+ },
+ {
+ "id": "nav-right",
+ "title": "Navigate right (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-nav.sh",
+ "right"
+ ]
+ },
+ {
+ "id": "nav-up",
+ "title": "Navigate up (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-nav.sh",
+ "up"
+ ]
+ },
+ {
+ "id": "resize-down",
+ "title": "Resize down (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-resize.sh",
+ "down"
+ ]
+ },
+ {
+ "id": "resize-left",
+ "title": "Resize left (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-resize.sh",
+ "left"
+ ]
+ },
+ {
+ "id": "resize-right",
+ "title": "Resize right (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-resize.sh",
+ "right"
+ ]
+ },
+ {
+ "id": "resize-up",
+ "title": "Resize up (Neovim/Herder)",
+ "contexts": [
+ "global"
+ ],
+ "command": [
+ "bash",
+ "scripts/herdr-resize.sh",
+ "up"
+ ]
+ }
+ ],
+ "source": {
+ "kind": "github",
+ "owner": "lmilojevicc",
+ "repo": "herdr-splits.nvim",
+ "resolved_commit": "107273e004e4f7ef07f13c83164d2cb2c51df65d",
+ "managed_path": "/Users/zamp/.config/herdr/plugins/github/herdr-splits-d880c4924f2b",
+ "installed_unix_ms": 1784924593009
+ }
+ }
+] \ No newline at end of file
diff --git a/dot_config/herdr/plugins/config/herdr-splits/herdr-splits.conf b/dot_config/herdr/plugins/config/herdr-splits/herdr-splits.conf
new file mode 100644
index 0000000..c16b7dc
--- /dev/null
+++ b/dot_config/herdr/plugins/config/herdr-splits/herdr-splits.conf
@@ -0,0 +1,15 @@
+# herdr-splits-generated-v1
+# herdr-splits plugin config — GENERATED by herdr-splits.nvim setup().
+# Regenerated from defaults + setup() opts on every setup(); do not edit by hand.
+# A headerless/legacy conf is adopted once on migration (see the startup
+# notice), then overwritten thereafter — remove an opt in setup() to revert.
+nav_key_left=ctrl+h
+nav_key_down=ctrl+j
+nav_key_up=ctrl+k
+nav_key_right=ctrl+l
+resize_key_left=alt+h
+resize_key_down=alt+j
+resize_key_up=alt+k
+resize_key_right=alt+l
+unzoom_on_nav=true
+nav_at_edge=wrap
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/CONTRIBUTING.md b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/CONTRIBUTING.md
new file mode 100644
index 0000000..3cdd672
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/CONTRIBUTING.md
@@ -0,0 +1,126 @@
+# Contributing to herdr-splits.nvim
+
+Thanks for your interest in improving herdr-splits.nvim! This guide covers how
+to set up a local development environment and submit changes.
+
+herdr-splits.nvim is a Neovim plugin (Lua) that also ships a Herdr-side plugin
+(the bash scripts under `scripts/` plus the `herdr-plugin.toml` manifest). The
+two halves cooperate — please consider both when making changes to navigation
+or resize behaviour.
+
+## Requirements
+
+- Neovim ≥ 0.10
+- [Herdr](https://herdr.dev) ≥ 0.7.0 (only needed to exercise cross-pane
+ behaviour; you can develop the Lua side without a running Herdr session)
+- A Herdr-compatible terminal with Option/Alt enabled (see the README's macOS
+ notes if you're on macOS)
+
+## Repository layout
+
+```
+plugin/herdr-splits.lua Neovim entrypoint
+lua/herdr-splits/ Neovim plugin source
+ init.lua setup() + public API
+ nav.lua resize.lua navigation and resizing
+ win.lua float / sidebar classification
+ sync.lua Herdr-side script syncing
+ config.lua herdr.lua config + core
+ health.lua :checkhealth herdr-splits
+scripts/herdr-nav.sh Herdr-side navigation action
+scripts/herdr-resize.sh Herdr-side resize action
+scripts/minimal_init.lua Isolated Neovim test bootstrap
+tests/ mini.test suites
+Makefile Local and CI validation commands
+herdr-plugin.toml Herdr plugin manifest (actions + metadata)
+```
+
+## Local development setup
+
+### Neovim side
+
+Point your plugin manager at a local clone instead of the published repo:
+
+```lua
+-- lazy.nvim
+{
+ dir = '~/Projects/herdr-splits',
+ cond = vim.env.HERDR_ENV == '1',
+ config = function()
+ require('herdr-splits').setup({})
+ end,
+}
+```
+
+Changes to files under `lua/` take effect after `:source` or a Neovim restart
+(unless your plugin manager hot-reloads).
+
+### Herdr side
+
+Link the clone so Herdr loads its scripts from your working tree:
+
+```bash
+herdr plugin link /path/to/herdr-splits
+```
+
+After changing `scripts/*.sh` or `herdr-plugin.toml`, run
+`herdr server reload-config` to pick them up.
+
+## Coding style
+
+- Match the surrounding Lua style — indentation, quoting, and table layout in
+ the file you're editing are the source of truth.
+- Run [StyLua](https://github.com/JohnnyMorganz/StyLua) if you use it, but note
+ that no `.stylua.toml` is committed yet; when one is added it will be the
+ canonical formatter config.
+- Run [luacheck](https://github.com/mpeterv/luacheck) where practical to catch
+ undefined globals and unused variables.
+- Keep the bash scripts POSIX-friendly where possible; they run on both Linux
+ and macOS as declared in `herdr-plugin.toml`.
+
+## Validation
+
+The Make targets initialize and update the pinned test dependency automatically.
+To prepare it separately, run `make deps`.
+
+Run the automated checks with:
+
+```bash
+make test # isolated mini.test suite in headless Neovim
+make check-shell # bash syntax checks for both shipped scripts
+make check # all of the above
+```
+
+Cross-pane behaviour still requires manual validation in a Herdr session:
+
+1. Load the plugin in Neovim inside a Herdr session (`HERDR_ENV=1`).
+2. Run `:checkhealth herdr-splits` and confirm it reports no failures.
+3. Exercise navigation (`<C-h/j/k/l>`) and resizing (`<M-h/j/k/l>`) across both
+ Neovim splits and Herdr panes, including the edge cases you changed:
+ - `at_edge` behaviour (`wrap` / `stop` / `split` / custom)
+ - count prefixes (e.g. `3<C-h>`)
+ - floating windows and embedded-sidebar floats
+ - auto-unzoom when crossing into a sibling Herdr pane
+
+## Submitting changes
+
+1. Fork the repo and create a branch from `main`.
+2. Make focused commits with descriptive messages.
+3. Open a pull request against `main` and fill in the PR template — include the
+ manual validation steps you ran.
+4. If your change affects user-facing behaviour or config, update `README.md`
+ and, for manifest-level changes, `herdr-plugin.toml`.
+
+Keep PRs scoped to one concern. Large changes are easier to review when split
+into smaller, independently understandable commits.
+
+## Reporting bugs and ideas
+
+Use the GitHub issue templates (bug report / feature request). Include the
+output of `nvim --version`, your plugin manager, the Herdr version
+(`herdr --version`), and a minimal reproduction.
+
+## License
+
+By contributing you agree that your contributions are licensed under the MIT
+License, the same terms as the rest of the project (see `LICENSE`).
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/LICENSE b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/LICENSE
new file mode 100644
index 0000000..511d507
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Luka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/Makefile b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/Makefile
new file mode 100644
index 0000000..d8a3be9
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/Makefile
@@ -0,0 +1,21 @@
+ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+NVIM ?= nvim
+
+.PHONY: deps test check-shell check
+
+deps:
+ @if git -C "$(ROOT)" ls-files --error-unmatch deps/mini.test >/dev/null 2>&1; then \
+ git -C "$(ROOT)" submodule update --init --depth 1 --checkout deps/mini.test; \
+ else \
+ test -f "$(ROOT)/deps/mini.test/lua/mini/test.lua"; \
+ fi
+
+test: deps
+ cd "$(ROOT)" && "$(NVIM)" --headless --noplugin -i NONE \
+ -u "$(ROOT)/scripts/minimal_init.lua" -c "lua MiniTest.run()"
+
+check-shell:
+ bash -n "$(ROOT)/scripts/herdr-nav.sh"
+ bash -n "$(ROOT)/scripts/herdr-resize.sh"
+
+check: check-shell test
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/README.md b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/README.md
new file mode 100644
index 0000000..7f9cd53
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/README.md
@@ -0,0 +1,445 @@
+# herdr-splits.nvim
+
+Seamless navigation and resizing between Neovim splits and [Herdr](https://herdr.dev) panes. Makes Herdr terminal splits behave like native Neovim windows — move and resize as if they were all part of the same editor.
+
+Inspired by [smart-splits.nvim](https://github.com/mrjones2014/smart-splits.nvim), ported to Herdr's CLI.
+
+## Features
+
+- **Seamless navigation**: Same keys work in Neovim and plain shells — Herdr forwards them to Neovim when appropriate, Neovim delegates to Herdr at edges.
+- **Seamless resizing**: `<M-h/j/k/l>` resizes Neovim splits natively, delegates to Herdr when a window fills the terminal.
+- **at_edge behaviours**: `wrap` (default), `stop`, `split`, or a custom function.
+- **Plugin-aware**: Ignores snacks/neo-tree/dadbod-ui/aerial sidebars and embedded floats (zindex < 50) by default — your keybinds never get trapped inside a picker.
+- **Smart unzoom + wrap-around**: Auto-unzooms when leaving a zoomed pane. At a layout edge, navigation wraps to the opposite side — crossing into a sibling Herdr pane when one exists (e.g. `pane | nvim` → `ctrl+l` wraps to the pane), otherwise wrapping within Neovim. Herdr-pane navigation also wraps around at edges (past the last pane → the first). Does not unzoom when moving between Neovim splits in one pane.
+- **Count prefix support**: `3<C-h>` moves three splits left; `5<M-l>` resizes five steps right.
+
+## Requirements
+
+- Neovim ≥ 0.10
+- [Herdr](https://herdr.dev) ≥ 0.7.0 (for plugin actions)
+
+## Installation
+
+Three steps — install on both the Herdr side and the Neovim side, then add keybinds.
+
+### 1. Install the Herdr plugin
+
+This provides the `nav-*` and `resize-*` actions that Herdr keybinds invoke.
+
+```bash
+# From GitHub (once published):
+herdr plugin install lmilojevicc/herdr-splits.nvim
+
+# Local development:
+herdr plugin link /path/to/herdr-splits
+```
+
+### 2. Install the Neovim plugin
+
+## lazy.nvim
+
+```lua
+{
+ 'lmilojevicc/herdr-splits.nvim',
+ -- For local development, swap the repo line for `dir = '/path/to/herdr-splits'`
+ -- (see "Local development" below).
+ cond = vim.env.HERDR_ENV == '1',
+ event = 'VeryLazy',
+ -- Optional: auto-sync the Herdr-side scripts when lazy updates this plugin.
+ -- Requires `auto_sync_herdr = true` in setup() below to take effect.
+ -- build = 'lua require("herdr-splits").sync_herdr()',
+ config = function()
+ require('herdr-splits').setup({
+ -- Defaults shown. All fields optional.
+ default_amount = 0.03, -- Herdr resize ratio
+ neovim_amount = 3, -- Neovim resize cells
+ at_edge = 'wrap', -- 'wrap' | 'stop' | 'split' | function
+ ignored_buftypes = { 'nofile', 'quickfix', 'prompt', 'help', 'terminal' },
+ ignored_filetypes = {
+ 'NvimTree',
+ -- sidebars
+ 'neo-tree',
+ 'snacks_dashboard',
+ 'snacks_explorer',
+ 'snacks_picker',
+ -- DB / REPL / data sidebars
+ 'dadbod-ui',
+ 'dbout',
+ -- outlines / symbols
+ 'aerial',
+ 'Outline',
+ -- diagnostics / quick lists
+ 'Trouble',
+ 'quickfix',
+ },
+ move_cursor_same_row = false,
+ herdr_bin = nil, -- auto-detected from HERDR_BIN_PATH
+ floating_zindex_max = 50, -- floats with zindex < this are treated as embedded sidebars
+ ignore_previewwindows = false, -- opt-in: also treat previewwindow windows (e.g. .dbout) as sidebars
+ -- auto_sync_herdr = true, -- opt-in: sync Herdr-side scripts on update
+ -- Managed keys — written to the generated herdr-splits.conf so the
+ -- Herdr-side scripts agree. Pass Neovim notation (e.g. <M-Left>).
+ nav_keys = { left = '<C-h>', down = '<C-j>', up = '<C-k>', right = '<C-l>' },
+ resize_keys = { left = '<M-h>', down = '<M-j>', up = '<M-k>', right = '<M-l>' },
+ unzoom_on_nav = true, -- auto-unzoom when navigating away from a zoomed pane
+ nav_at_edge = 'wrap', -- 'wrap' | 'stop' — Herdr pane-boundary wrap (distinct from at_edge)
+ })
+ end,
+ keys = {
+ { '<C-h>', function() require('herdr-splits').move_cursor_left() end, desc = 'Navigate left' },
+ { '<C-j>', function() require('herdr-splits').move_cursor_down() end, desc = 'Navigate down' },
+ { '<C-k>', function() require('herdr-splits').move_cursor_up() end, desc = 'Navigate up' },
+ { '<C-l>', function() require('herdr-splits').move_cursor_right() end, desc = 'Navigate right' },
+ { '<M-h>', function() require('herdr-splits').resize_left() end, desc = 'Resize left' },
+ { '<M-j>', function() require('herdr-splits').resize_down() end, desc = 'Resize down' },
+ { '<M-k>', function() require('herdr-splits').resize_up() end, desc = 'Resize up' },
+ { '<M-l>', function() require('herdr-splits').resize_right() end, desc = 'Resize right' },
+ },
+}
+```
+
+## packer.nvim
+
+```lua
+use {
+ 'lmilojevicc/herdr-splits.nvim',
+ config = function()
+ require('herdr-splits').setup()
+ end,
+}
+```
+
+### 3. Add keybinds to Herdr config
+
+Add these to `~/.config/herdr/config.toml`, then run `herdr server reload-config`:
+
+```toml
+[[keys.command]]
+key = "ctrl+h"
+type = "plugin_action"
+command = "herdr-splits.nav-left"
+
+[[keys.command]]
+key = "ctrl+j"
+type = "plugin_action"
+command = "herdr-splits.nav-down"
+
+[[keys.command]]
+key = "ctrl+k"
+type = "plugin_action"
+command = "herdr-splits.nav-up"
+
+[[keys.command]]
+key = "ctrl+l"
+type = "plugin_action"
+command = "herdr-splits.nav-right"
+
+[[keys.command]]
+key = "alt+h"
+type = "plugin_action"
+command = "herdr-splits.resize-left"
+
+[[keys.command]]
+key = "alt+j"
+type = "plugin_action"
+command = "herdr-splits.resize-down"
+
+[[keys.command]]
+key = "alt+k"
+type = "plugin_action"
+command = "herdr-splits.resize-up"
+
+[[keys.command]]
+key = "alt+l"
+type = "plugin_action"
+command = "herdr-splits.resize-right"
+```
+
+> **Note for macOS**: Terminals treat Option as a special character modifier by default. You need to set Option = Alt/Meta:
+>
+> - **Ghostty**: `macos-option-as-alt = true`
+> - **Alacritty**: `option_as_alt = "Both"`
+> - **kitty**: `macos_option_as_alt yes`
+> - **iTerm**: Profiles → Keys → Left Option key → Esc+
+>
+> If you're using [smart-splits.nvim](https://github.com/mrjones2014/smart-splits.nvim) for tmux, add `cond = vim.env.HERDR_ENV ~= '1'` to its spec so the two plugins don't conflict.
+
+### Auto-unzoom and wrap-around
+
+**Auto-unzoom.** When you navigate away from a **zoomed** pane, herdr-splits
+unzooms first so the destination is visible — whether you're crossing into a
+sibling Herdr pane or wrapping at a layout edge. It does not unzoom when
+moving between Neovim splits inside the same pane.
+
+**Wrap-around.** When you reach a layout edge in the direction you pressed
+(nowhere further to go that way), the default `wrap` behaviour continues on
+the opposite side:
+
+- If a Herdr pane sits on the opposite side, focus crosses into it — so
+ `pane | nvim` wraps `ctrl+l` back to the pane, and
+ `pane | nvim win1 | win2` wraps from win2 to the pane. With `nav_at_edge=stop`
+ (see below) this cross-to-opposite-pane is suppressed and the wrap stays
+ within Neovim instead.
+- Otherwise it wraps within Neovim (smart-splits default).
+- Between plain Herdr panes, navigation wraps around at edges too (past the
+ last pane → the first).
+- Wrap works even when you're on a sidebar (dbui, neo-tree, quickfix, ...),
+ so you can leave it at an edge; only embedded floating overlays are gated.
+
+**To disable auto-unzoom entirely**, pass `unzoom_on_nav = false` to `setup()`:
+
+```lua
+require('herdr-splits').setup({ unzoom_on_nav = false })
+```
+
+These options are written to a generated `herdr-splits.conf` (default
+`~/.config/herdr/plugins/config/herdr-splits/herdr-splits.conf`; print the
+path with `herdr plugin config-dir herdr-splits`) so the Herdr-side scripts
+agree — you configure them once, from `setup()`. **Do not hand-edit that
+file:** it is regenerated on every `setup()`. Any values already present are
+adopted once the first time `setup()` runs after an upgrade, then
+overwritten thereafter. Set `HERDR_SPLITS_CONFIG` to override the path on
+both sides.
+
+**To stop at layout edges instead of wrapping**, pass `nav_at_edge = 'stop'`:
+
+```lua
+require('herdr-splits').setup({ nav_at_edge = 'stop' })
+```
+
+This single switch controls wrap-across-boundary on **both** sides:
+
+- **Plain Herdr panes:** `wrap` (the default) wraps to the opposite pane at an
+ edge; `stop` halts.
+- **Neovim edge wrap:** when Neovim's `at_edge='wrap'`, `wrap` (the default)
+ lets the wrap cross to the Herdr pane on the opposite side (e.g. `ctrl+l` from
+ the last nvim split lands on the pane); `stop` keeps the wrap within Neovim
+ (e.g. `ctrl+l` from the last split cycles to the first split instead of
+ leaving Neovim). `at_edge='stop'` halts regardless of `nav_at_edge`.
+
+Note: `at_edge` (Neovim window-edge behaviour: `wrap`/`stop`/`split`/function)
+and `nav_at_edge` (Herdr pane-boundary wrap: `wrap`/`stop`) are two different
+things despite the shared name — `at_edge` is Neovim-side only and is not
+written to the conf.
+
+Note: when a pane is zoomed and `unzoom_on_nav=false`, the edge flags can't be
+trusted so `stop` can't be detected on the plain-pane side — navigation proceeds
+in the requested direction in that case. With the default `unzoom_on_nav=true`,
+pressing toward an edge while zoomed first unzooms the pane (so the edge can be
+detected) and then halts — the pane unzooms even though focus doesn't move.
+
+**To remap the keys forwarded into Neovim**, pass `nav_keys` / `resize_keys`
+to `setup()` in Neovim notation (e.g. `<M-Left>`). Override only the
+directions you rebind; the rest keep their defaults. By default the scripts
+forward `ctrl+h/j/k/l` for navigation and `alt+h/j/k/l` for resizing — the
+chords the documented Neovim keymaps bind:
+
+```lua
+require('herdr-splits').setup({
+ nav_keys = { left = '<M-Left>', down = '<M-Down>', up = '<M-Up>', right = '<M-Right>' },
+ resize_keys = { left = '<M-S-Left>', down = '<M-S-Down>', up = '<M-S-Up>', right = '<M-S-Right>' },
+})
+```
+
+- Pass Neovim notation in `setup()` (`<C-h>`, `<M-Left>`); it is translated to the Herdr chord notation (`ctrl+h`, `alt+left`) the generated conf writes and `herdr pane send-keys` accepts.
+- Any direction left unset keeps its default.
+- The chord forwarded into Neovim must match the Neovim keymap that catches it: `<M-Left>` in `setup()` is the same key `alt+left` the script forwards, so keep your `vim.keymap.set` in sync with `nav_keys`.
+
+> **Eager load required for custom keys.** The generated conf is written by
+> `setup()`, so custom `nav_keys`/`resize_keys` need `setup()` to run eagerly
+> or at `VeryLazy` — do **not** lazy-load this plugin *only* from the same
+> custom key mappings it must publish. Otherwise the conf carrying your custom
+> chords isn't written before Herdr forwards them, and those keys can't reach
+> Neovim to trigger the lazy load. The recommended spec above already uses
+> `event = 'VeryLazy'`.
+
+## Local development
+
+Both sides load straight from your clone, so edits take effect immediately
+(on the Herdr side; Neovim picks them up on reload/restart).
+
+1. **Link the Herdr plugin** to your local repo:
+
+ ```bash
+ herdr plugin link /path/to/herdr-splits
+ ```
+
+2. **Point lazy.nvim at the same path** — change the first line of the spec
+ from the GitHub repo to `dir`:
+
+ ```lua
+ {
+ dir = '/path/to/herdr-splits',
+ cond = vim.env.HERDR_ENV == '1',
+ -- ...rest of the spec unchanged
+ }
+ ```
+
+Run `herdr plugin list` to confirm it shows `herdr-splits ... [local:/...]`.
+To switch back to the published version: `herdr plugin unlink herdr-splits`,
+then `herdr plugin install lmilojevicc/herdr-splits.nvim`, and revert the
+lazy spec to `'lmilojevicc/herdr-splits.nvim'`.
+
+## Lua API
+
+```lua
+require('herdr-splits').setup(opts)
+
+-- Resize (amount defaults to config.default_amount, multiplied by count prefix)
+require('herdr-splits').resize_left(amount)
+require('herdr-splits').resize_down(amount)
+require('herdr-splits').resize_up(amount)
+require('herdr-splits').resize_right(amount)
+
+-- Navigate (opts can override same_row and at_edge per-call)
+require('herdr-splits').move_cursor_left({ same_row = true, at_edge = 'stop' })
+require('herdr-splits').move_cursor_down(opts)
+require('herdr-splits').move_cursor_up(opts)
+require('herdr-splits').move_cursor_right(opts)
+```
+
+## Compatibility
+
+`herdr-splits.nvim` ships with an opinionated default `ignored_filetypes` list so your resize keybinds don't get trapped inside common sidebars and pickers. These filetypes are treated as sidebars: `split`-at-edge and Herdr resize delegation are skipped from them, and the flag is passed to a custom `at_edge` function as `is_sidebar`. `wrap`, however, still works from a sidebar so you can leave it (e.g. `ctrl+l` on a `dadbod-ui` drawer wraps to the opposite side); only embedded floating overlays are gated.
+
+The default `ignored_filetypes` covers:
+
+- `NvimTree` — `nvim-tree/nvim-tree.lua` default file tree (`buftype=nofile`, `winfixwidth`).
+- `neo-tree` — `nvim-neo-tree/neo-tree.nvim` LazyVim default (`winfixwidth` sidebar).
+- `snacks_dashboard` / `snacks_explorer` / `snacks_picker` — `folke/snacks` sidebar/picker windows (the float case is handled by `is_embedded_floating_window` below).
+- `dadbod-ui` / `dbout` — `kristijanhusak/vim-dadbod-ui` drawer and `tpope/vim-dadbod` preview result buffer.
+- `aerial` / `Outline` — code-outline sidebars.
+- `Trouble` / `quickfix` — diagnostics and quickfix lists.
+
+The default `ignored_buftypes` covers `nofile`, `quickfix`, `prompt`, `help`, and `terminal`.
+
+**Embedded floating windows** (snacks explorer in float mode, neo-tree in float mode, aerial in float mode) are detected via the `zindex < floating_zindex_max` heuristic (default threshold 50, Neovim's default float zindex) and treated as sidebars for navigation/resize decisions. Override the threshold via the `floating_zindex_max` config field.
+
+**Add your own at runtime** from any `VeryLazy` autocmd:
+
+```lua
+require('herdr-splits').add_ignored_filetype('lazy')
+require('herdr-splits').add_ignored_buftype('help')
+```
+
+**Debug with `:checkhealth herdr-splits`** (Neovim ≥ 0.10) to see which filetypes/buftypes are ignored, whether Herdr is in session, and whether the current window is classified as a sidebar.
+
+If you want to **completely replace** the default list (e.g. to opt out of one of the entries), note that `vim.tbl_deep_extend('force', M, opts)` **concatenates** the `ignored_filetypes` / `ignored_buftypes` tables you pass; you keep what you pass AND the defaults are appended. To opt out, assign directly after `setup()`:
+
+```lua
+require('herdr-splits').setup({ ignored_filetypes = {} }) -- inherit nothing
+require('herdr-splits').add_ignored_filetype('mything') -- then add back what you want
+```
+
+## How It Works
+
+Two sides cooperate for seamless two-way navigation:
+
+```text
+You press C-h in a Herdr pane:
+
+ ┌─ Herdr intercepts the key (plugin_action keybind)
+ │
+ ├─ Is the focused pane running Neovim?
+ │ │
+ │ ├─ YES → forward "ctrl+h" into that pane
+ │ │ └─ Neovim plugin receives it
+ │ │ ├─ Has a window to the left? → wincmd h
+ │ │ └─ At Neovim edge? → herdr pane focus --direction left
+ │ │
+ │ └─ NO (plain shell, etc.) → herdr pane focus --direction left
+ │
+ └─ Result: same keys move you everywhere
+```
+
+### Navigation
+
+```text
+1. Try moving within Neovim (wincmd h/j/k/l)
+ ├─ Window changed → done (stayed within Neovim splits)
+ └─ Window didn't change → at Neovim edge
+ ├─ Zoomed? → unzoom first (neighbours are reliable once visible)
+ ├─ Check if Herdr is running (HERDR_ENV=1)
+ ├─ Check if Herdr pane has a neighbour in this direction
+ │ └─ Yes → herdr pane focus --direction → done
+ └─ No (at both Neovim AND Herdr edge) →
+ apply at_edge behaviour (`wrap` crosses to the opposite Herdr
+ pane if one exists and nav_at_edge=wrap (the default), else
+ wraps within Neovim)
+```
+
+### Resizing
+
+```text
+1. Only one Neovim window in this dimension AND fills terminal?
+ └─ Yes → herdr pane resize --direction --amount <ratio> → done
+2. Otherwise:
+ └─ Native Neovim resize with position-aware +/- operators
+```
+
+### Float and embedded-float classification
+
+Two predicates classify the current window before any nav/resize decision:
+
+- `win.is_floating(winid)` — `nvim_win_get_config(winid).relative ~= ''` (Neovim's built-in float check).
+- `win.is_embedded_floating_window(winid)` — true only for floats with `zindex < floating_zindex_max` (default 50); matches snacks/neo-tree/aerial float-mode sidebars.
+
+True floats forward to Herdr; embedded floats behave as sidebars (no movement, no Herdr delegation).
+
+## Herdr Detection
+
+Detected automatically through environment variables Herdr injects into every pane:
+
+- `HERDR_ENV=1` — set when running inside a Herdr-managed pane
+- `HERDR_PANE_ID` — the public pane ID (e.g., `w1:p1`)
+- `HERDR_BIN_PATH` — path to the herdr binary (for subprocess calls)
+
+## Comparison with vim-herdr-navigation
+
+| Feature | vim-herdr-navigation | herdr-splits.nvim |
+| --------------------------- | -------------------- | ---------------------------- |
+| Navigation | ✓ | ✓ |
+| Resizing | ✗ | ✓ |
+| at_edge behaviours | ✗ | wrap / stop / split / custom |
+| Count prefix | ✗ | ✓ (3<C-h> = move 3 left) |
+| Auto-unzoom + wrap-around | ✗ | ✓ |
+| Floating window handling | ✗ | ✓ |
+| Herdr plugin (for keybinds) | ✓ | ✓ |
+| Neovim plugin | ✓ | ✓ |
+
+## Updating
+
+The Neovim side (lua) auto-updates via lazy.nvim — it pulls `main` on
+`LazySync`. The Herdr side (the bash scripts under the Herdr-managed
+checkout) does **not** auto-update by default: Herdr v1 has no
+`herdr plugin update`, so a managed checkout stays frozen at the commit it
+was installed from.
+
+**Option A — opt into automatic sync (recommended):** set
+`auto_sync_herdr = true` in `setup()`. The plugin then reinstalls the
+Herdr-managed checkout pinned to the exact commit lazy fetched, so the bash
+scripts always match the lua side. It is a no-op when already in sync, when
+in local-dev (`plugin link`) mode, or when the `herdr` binary is unavailable.
+
+For maximum efficiency, also add a `build` hook so the sync fires only when
+lazy actually updates the plugin, instead of on every Neovim startup:
+
+```lua
+{
+ 'lmilojevicc/herdr-splits.nvim',
+ build = 'lua require("herdr-splits").sync_herdr()',
+ config = function()
+ require('herdr-splits').setup({ auto_sync_herdr = true })
+ end,
+}
+```
+
+**Option B — manual:** after an update, re-run
+`herdr plugin install lmilojevicc/herdr-splits.nvim` to refresh the
+Herdr-side scripts.
+
+Release notes for each version live in [GitHub Releases](https://github.com/lmilojevicc/herdr-splits.nvim/releases).
+
+## License
+
+MIT
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/deps/mini.test/.keep b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/deps/mini.test/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/deps/mini.test/.keep
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/FETCH_HEAD b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/FETCH_HEAD
new file mode 100644
index 0000000..c02fa3c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/FETCH_HEAD
@@ -0,0 +1 @@
+107273e004e4f7ef07f13c83164d2cb2c51df65d https://github.com/lmilojevicc/herdr-splits.nvim
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/HEAD b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/HEAD
new file mode 100644
index 0000000..c367d86
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/HEAD
@@ -0,0 +1 @@
+107273e004e4f7ef07f13c83164d2cb2c51df65d
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/config b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/config
new file mode 100644
index 0000000..35a60e8
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/config
@@ -0,0 +1,10 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = true
+[remote "origin"]
+ url = https://github.com/lmilojevicc/herdr-splits.nvim.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/description b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/description
new file mode 100644
index 0000000..498b267
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_applypatch-msg.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_applypatch-msg.sample
new file mode 100644
index 0000000..a5d7b84
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_applypatch-msg.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit. The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
+:
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_commit-msg.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_commit-msg.sample
new file mode 100644
index 0000000..b58d118
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_commit-msg.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_fsmonitor-watchman.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_fsmonitor-watchman.sample
new file mode 100644
index 0000000..23e856f
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_fsmonitor-watchman.sample
@@ -0,0 +1,174 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 2) and last update token
+# formatted as a string and outputs to stdout a new update token and
+# all files that have been modified since the update token. Paths must
+# be relative to the root of the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $last_update_token) = @ARGV;
+
+# Uncomment for debugging
+# print STDERR "$0 $version $last_update_token\n";
+
+# Check the hook interface version
+if ($version ne 2) {
+ die "Unsupported query-fsmonitor hook version '$version'.\n" .
+ "Falling back to scanning...\n";
+}
+
+my $git_work_tree = get_working_dir();
+
+my $retry = 1;
+
+my $json_pkg;
+eval {
+ require JSON::XS;
+ $json_pkg = "JSON::XS";
+ 1;
+} or do {
+ require JSON::PP;
+ $json_pkg = "JSON::PP";
+};
+
+launch_watchman();
+
+sub launch_watchman {
+ my $o = watchman_query();
+ if (is_work_tree_watched($o)) {
+ output_result($o->{clock}, @{$o->{files}});
+ }
+}
+
+sub output_result {
+ my ($clockid, @files) = @_;
+
+ # Uncomment for debugging watchman output
+ # open (my $fh, ">", ".git/watchman-output.out");
+ # binmode $fh, ":utf8";
+ # print $fh "$clockid\n@files\n";
+ # close $fh;
+
+ binmode STDOUT, ":utf8";
+ print $clockid;
+ print "\0";
+ local $, = "\0";
+ print @files;
+}
+
+sub watchman_clock {
+ my $response = qx/watchman clock "$git_work_tree"/;
+ die "Failed to get clock id on '$git_work_tree'.\n" .
+ "Falling back to scanning...\n" if $? != 0;
+
+ return $json_pkg->new->utf8->decode($response);
+}
+
+sub watchman_query {
+ my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
+ or die "open2() failed: $!\n" .
+ "Falling back to scanning...\n";
+
+ # In the query expression below we're asking for names of files that
+ # changed since $last_update_token but not from the .git folder.
+ #
+ # To accomplish this, we're using the "since" generator to use the
+ # recency index to select candidate nodes and "fields" to limit the
+ # output to file names only. Then we're using the "expression" term to
+ # further constrain the results.
+ my $last_update_line = "";
+ if (substr($last_update_token, 0, 1) eq "c") {
+ $last_update_token = "\"$last_update_token\"";
+ $last_update_line = qq[\n"since": $last_update_token,];
+ }
+ my $query = <<" END";
+ ["query", "$git_work_tree", {$last_update_line
+ "fields": ["name"],
+ "expression": ["not", ["dirname", ".git"]]
+ }]
+ END
+
+ # Uncomment for debugging the watchman query
+ # open (my $fh, ">", ".git/watchman-query.json");
+ # print $fh $query;
+ # close $fh;
+
+ print CHLD_IN $query;
+ close CHLD_IN;
+ my $response = do {local $/; <CHLD_OUT>};
+
+ # Uncomment for debugging the watch response
+ # open ($fh, ">", ".git/watchman-response.json");
+ # print $fh $response;
+ # close $fh;
+
+ die "Watchman: command returned no output.\n" .
+ "Falling back to scanning...\n" if $response eq "";
+ die "Watchman: command returned invalid output: $response\n" .
+ "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+ return $json_pkg->new->utf8->decode($response);
+}
+
+sub is_work_tree_watched {
+ my ($output) = @_;
+ my $error = $output->{error};
+ if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
+ $retry--;
+ my $response = qx/watchman watch "$git_work_tree"/;
+ die "Failed to make watchman watch '$git_work_tree'.\n" .
+ "Falling back to scanning...\n" if $? != 0;
+ $output = $json_pkg->new->utf8->decode($response);
+ $error = $output->{error};
+ die "Watchman: $error.\n" .
+ "Falling back to scanning...\n" if $error;
+
+ # Uncomment for debugging watchman output
+ # open (my $fh, ">", ".git/watchman-output.out");
+ # close $fh;
+
+ # Watchman will always return all files on the first query so
+ # return the fast "everything is dirty" flag to git and do the
+ # Watchman query just to get it over with now so we won't pay
+ # the cost in git to look up each individual file.
+ my $o = watchman_clock();
+ $error = $output->{error};
+
+ die "Watchman: $error.\n" .
+ "Falling back to scanning...\n" if $error;
+
+ output_result($o->{clock}, ("/"));
+ $last_update_token = $o->{clock};
+
+ eval { launch_watchman() };
+ return 0;
+ }
+
+ die "Watchman: $error.\n" .
+ "Falling back to scanning...\n" if $error;
+
+ return 1;
+}
+
+sub get_working_dir {
+ my $working_dir;
+ if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+ $working_dir = Win32::GetCwd();
+ $working_dir =~ tr/\\/\//;
+ } else {
+ require Cwd;
+ $working_dir = Cwd::cwd();
+ }
+
+ return $working_dir;
+}
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_post-update.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_post-update.sample
new file mode 100644
index 0000000..ec17ec1
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_post-update.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git update-server-info
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-applypatch.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-applypatch.sample
new file mode 100644
index 0000000..4142082
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-applypatch.sample
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+precommit="$(git rev-parse --git-path hooks/pre-commit)"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
+:
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-commit.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-commit.sample
new file mode 100644
index 0000000..29ed5ee
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-commit.sample
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=$(git hash-object -t tree /dev/null)
+fi
+
+# If you want to allow non-ASCII filenames set this variable to true.
+allownonascii=$(git config --type=bool hooks.allownonascii)
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Cross platform projects tend to avoid non-ASCII filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+ # Note that the use of brackets around a tr range is ok here, (it's
+ # even required, for portability to Solaris 10's /usr/bin/tr), since
+ # the square bracket bytes happen to fall in the designated range.
+ test $(git diff-index --cached --name-only --diff-filter=A -z $against |
+ LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
+then
+ cat <<\EOF
+Error: Attempt to add a non-ASCII file name.
+
+This can cause problems if you want to work with people on other platforms.
+
+To be portable it is advisable to rename the file.
+
+If you know what you are doing you can disable this check using:
+
+ git config hooks.allownonascii true
+EOF
+ exit 1
+fi
+
+# If there are whitespace errors, print the offending file names and fail.
+exec git diff-index --check --cached $against --
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-merge-commit.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-merge-commit.sample
new file mode 100644
index 0000000..399eab1
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-merge-commit.sample
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git merge" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message to
+# stderr if it wants to stop the merge commit.
+#
+# To enable this hook, rename this file to "pre-merge-commit".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+ exec "$GIT_DIR/hooks/pre-commit"
+:
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-push.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-push.sample
new file mode 100644
index 0000000..4ce688d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-push.sample
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+# An example hook script to verify what is about to be pushed. Called by "git
+# push" after it has checked the remote status, but before anything has been
+# pushed. If this script exits with a non-zero status nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+# If pushing without using a named remote those arguments will be equal.
+#
+# Information about the commits which are being pushed is supplied as lines to
+# the standard input in the form:
+#
+# <local ref> <local oid> <remote ref> <remote oid>
+#
+# This sample shows how to prevent push of commits where the log message starts
+# with "WIP" (work in progress).
+
+remote="$1"
+url="$2"
+
+zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
+
+while read local_ref local_oid remote_ref remote_oid
+do
+ if test "$local_oid" = "$zero"
+ then
+ # Handle delete
+ :
+ else
+ if test "$remote_oid" = "$zero"
+ then
+ # New branch, examine all commits
+ range="$local_oid"
+ else
+ # Update to existing branch, examine new commits
+ range="$remote_oid..$local_oid"
+ fi
+
+ # Check for WIP commit
+ commit=$(git rev-list -n 1 --grep '^WIP' "$range")
+ if test -n "$commit"
+ then
+ echo >&2 "Found WIP commit in $local_ref, not pushing"
+ exit 1
+ fi
+ fi
+done
+
+exit 0
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-rebase.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-rebase.sample
new file mode 100644
index 0000000..6cbef5c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-rebase.sample
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+ topic="refs/heads/$2"
+else
+ topic=`git symbolic-ref HEAD` ||
+ exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+ ;;
+*)
+ exit 0 ;# we do not interrupt others.
+ ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master. Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+ echo >&2 "No such branch $topic"
+ exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+ echo >&2 "$topic is fully merged to master; better remove it."
+ exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next? If so you should not be rebasing it.
+only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git rev-list ^master ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+ not_in_topic=`git rev-list "^$topic" master`
+ if test -z "$not_in_topic"
+ then
+ echo >&2 "$topic is already up to date with master"
+ exit 1 ;# we could allow it, but there is no point.
+ else
+ exit 0
+ fi
+else
+ not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+ /usr/bin/perl -e '
+ my $topic = $ARGV[0];
+ my $msg = "* $topic has commits already merged to public branch:\n";
+ my (%not_in_next) = map {
+ /^([0-9a-f]+) /;
+ ($1 => 1);
+ } split(/\n/, $ARGV[1]);
+ for my $elem (map {
+ /^([0-9a-f]+) (.*)$/;
+ [$1 => $2];
+ } split(/\n/, $ARGV[2])) {
+ if (!exists $not_in_next{$elem->[0]}) {
+ if ($msg) {
+ print STDERR $msg;
+ undef $msg;
+ }
+ print STDERR " $elem->[1]\n";
+ }
+ }
+ ' "$topic" "$not_in_next" "$not_in_master"
+ exit 1
+fi
+
+<<\DOC_END
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+ merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+ it is deleted. If you need to build on top of it to correct
+ earlier mistakes, a new topic branch is created by forking at
+ the tip of the "master". This is not strictly necessary, but
+ it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+ branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next". Young
+ topic branches can have stupid mistakes you would rather
+ clean up before publishing, and things that have not been
+ merged into other branches can be easily rebased without
+ affecting other people. But once it is published, you would
+ not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+ Then you can delete it. More importantly, you should not
+ build on top of it -- other people may already want to
+ change things related to the topic as patches against your
+ "master", so if you need further changes, it is better to
+ fork the topic (perhaps with the same name) afresh from the
+ tip of "master".
+
+Let's look at this example:
+
+ o---o---o---o---o---o---o---o---o---o "next"
+ / / / /
+ / a---a---b A / /
+ / / / /
+ / / c---c---c---c B /
+ / / / \ /
+ / / / b---b C \ /
+ / / / / \ /
+ ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished. It has been fully merged up to "master" and "next",
+ and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+ git rev-list ^master ^topic next
+ git rev-list ^master next
+
+ if these match, topic has not merged in next at all.
+
+To compute (2):
+
+ git rev-list master..topic
+
+ if this is empty, it is fully merged to "master".
+
+DOC_END
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-receive.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-receive.sample
new file mode 100644
index 0000000..a1fd29e
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_pre-receive.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to make use of push options.
+# The example simply echoes all push options that start with 'echoback='
+# and rejects all pushes when the "reject" push option is used.
+#
+# To enable this hook, rename this file to "pre-receive".
+
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
+ i=0
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+ do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ case "$value" in
+ echoback=*)
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
+ ;;
+ reject)
+ exit 1
+ esac
+ i=$((i + 1))
+ done
+fi
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_prepare-commit-msg.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_prepare-commit-msg.sample
new file mode 100644
index 0000000..10fa14c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_prepare-commit-msg.sample
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by "git commit" with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source. The hook's purpose is to edit the commit
+# message file. If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples. The first one removes the
+# "# Please enter the commit message..." help message.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output. It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited. This is rarely a good idea.
+
+COMMIT_MSG_FILE=$1
+COMMIT_SOURCE=$2
+SHA1=$3
+
+/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
+
+# case "$COMMIT_SOURCE,$SHA1" in
+# ,|template,)
+# /usr/bin/perl -i.bak -pe '
+# print "\n" . `git diff --cached --name-status -r`
+# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
+# *) ;;
+# esac
+
+# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
+# if test -z "$COMMIT_SOURCE"
+# then
+# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
+# fi
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_push-to-checkout.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_push-to-checkout.sample
new file mode 100644
index 0000000..af5a0c0
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_push-to-checkout.sample
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+# An example hook script to update a checked-out tree on a git push.
+#
+# This hook is invoked by git-receive-pack(1) when it reacts to git
+# push and updates reference(s) in its repository, and when the push
+# tries to update the branch that is currently checked out and the
+# receive.denyCurrentBranch configuration variable is set to
+# updateInstead.
+#
+# By default, such a push is refused if the working tree and the index
+# of the remote repository has any difference from the currently
+# checked out commit; when both the working tree and the index match
+# the current commit, they are updated to match the newly pushed tip
+# of the branch. This hook is to be used to override the default
+# behaviour; however the code below reimplements the default behaviour
+# as a starting point for convenient modification.
+#
+# The hook receives the commit with which the tip of the current
+# branch is going to be updated:
+commit=$1
+
+# It can exit with a non-zero status to refuse the push (when it does
+# so, it must not modify the index or the working tree).
+die () {
+ echo >&2 "$*"
+ exit 1
+}
+
+# Or it can make any necessary changes to the working tree and to the
+# index to bring them to the desired state when the tip of the current
+# branch is updated to the new commit, and exit with a zero status.
+#
+# For example, the hook can simply run git read-tree -u -m HEAD "$1"
+# in order to emulate git fetch that is run in the reverse direction
+# with git push, as the two-tree form of git read-tree -u -m is
+# essentially the same as git switch or git checkout that switches
+# branches while keeping the local changes in the working tree that do
+# not interfere with the difference between the branches.
+
+# The below is a more-or-less exact translation to shell of the C code
+# for the default behaviour for git's push-to-checkout hook defined in
+# the push_to_deploy() function in builtin/receive-pack.c.
+#
+# Note that the hook will be executed from the repository directory,
+# not from the working tree, so if you want to perform operations on
+# the working tree, you will have to adapt your code accordingly, e.g.
+# by adding "cd .." or using relative paths.
+
+if ! git update-index -q --ignore-submodules --refresh
+then
+ die "Up-to-date check failed"
+fi
+
+if ! git diff-files --quiet --ignore-submodules --
+then
+ die "Working directory has unstaged changes"
+fi
+
+# This is a rough translation of:
+#
+# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
+if git cat-file -e HEAD 2>/dev/null
+then
+ head=HEAD
+else
+ head=$(git hash-object -t tree --stdin </dev/null)
+fi
+
+if ! git diff-index --quiet --cached --ignore-submodules $head --
+then
+ die "Working directory has staged changes"
+fi
+
+if ! git read-tree -u -m "$commit"
+then
+ die "Could not update working tree to new HEAD"
+fi
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_sendemail-validate.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_sendemail-validate.sample
new file mode 100644
index 0000000..640bcf8
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_sendemail-validate.sample
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# An example hook script to validate a patch (and/or patch series) before
+# sending it via email.
+#
+# The hook should exit with non-zero status after issuing an appropriate
+# message if it wants to prevent the email(s) from being sent.
+#
+# To enable this hook, rename this file to "sendemail-validate".
+#
+# By default, it will only check that the patch(es) can be applied on top of
+# the default upstream branch without conflicts in a secondary worktree. After
+# validation (successful or not) of the last patch of a series, the worktree
+# will be deleted.
+#
+# The following config variables can be set to change the default remote and
+# remote ref that are used to apply the patches against:
+#
+# sendemail.validateRemote (default: origin)
+# sendemail.validateRemoteRef (default: HEAD)
+#
+# Replace the TODO placeholders with appropriate checks according to your
+# needs.
+
+validate_cover_letter () {
+ file="$1"
+ # TODO: Replace with appropriate checks (e.g. spell checking).
+ true
+}
+
+validate_patch () {
+ file="$1"
+ # Ensure that the patch applies without conflicts.
+ git am -3 "$file" || return
+ # TODO: Replace with appropriate checks for this patch
+ # (e.g. checkpatch.pl).
+ true
+}
+
+validate_series () {
+ # TODO: Replace with appropriate checks for the whole series
+ # (e.g. quick build, coding style checks, etc.).
+ true
+}
+
+# main -------------------------------------------------------------------------
+
+if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
+then
+ remote=$(git config --default origin --get sendemail.validateRemote) &&
+ ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
+ worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
+ git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
+ git config --replace-all sendemail.validateWorktree "$worktree"
+else
+ worktree=$(git config --get sendemail.validateWorktree)
+fi || {
+ echo "sendemail-validate: error: failed to prepare worktree" >&2
+ exit 1
+}
+
+unset GIT_DIR GIT_WORK_TREE
+cd "$worktree" &&
+
+if grep -q "^diff --git " "$1"
+then
+ validate_patch "$1"
+else
+ validate_cover_letter "$1"
+fi &&
+
+if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
+then
+ git config --unset-all sendemail.validateWorktree &&
+ trap 'git worktree remove -ff "$worktree"' EXIT &&
+ validate_series
+fi
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_update.sample b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_update.sample
new file mode 100644
index 0000000..c4d426b
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/hooks/executable_update.sample
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# An example hook script to block unannotated tags from entering.
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+# This boolean sets whether unannotated tags will be allowed into the
+# repository. By default they won't be.
+# hooks.allowdeletetag
+# This boolean sets whether deleting tags will be allowed in the
+# repository. By default they won't be.
+# hooks.allowmodifytag
+# This boolean sets whether a tag may be modified after creation. By default
+# it won't be.
+# hooks.allowdeletebranch
+# This boolean sets whether deleting branches will be allowed in the
+# repository. By default they won't be.
+# hooks.denycreatebranch
+# This boolean sets whether remotely creating branches will be denied
+# in the repository. By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+ echo "Don't run this script from the command line." >&2
+ echo " (if you want, you could supply GIT_DIR then run" >&2
+ echo " $0 <ref> <oldrev> <newrev>)" >&2
+ exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+ echo "usage: $0 <ref> <oldrev> <newrev>" >&2
+ exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --type=bool hooks.allowunannotated)
+allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
+allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
+allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+ echo "*** Project description file hasn't been set" >&2
+ exit 1
+ ;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
+if [ "$newrev" = "$zero" ]; then
+ newrev_type=delete
+else
+ newrev_type=$(git cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+ refs/tags/*,commit)
+ # un-annotated tag
+ short_refname=${refname##refs/tags/}
+ if [ "$allowunannotated" != "true" ]; then
+ echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
+ echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,delete)
+ # delete tag
+ if [ "$allowdeletetag" != "true" ]; then
+ echo "*** Deleting a tag is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,tag)
+ # annotated tag
+ if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+ then
+ echo "*** Tag '$refname' already exists." >&2
+ echo "*** Modifying a tag is not allowed in this repository." >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,commit)
+ # branch
+ if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+ echo "*** Creating a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,delete)
+ # delete branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/remotes/*,commit)
+ # tracking branch
+ ;;
+ refs/remotes/*,delete)
+ # delete tracking branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ *)
+ # Anything else (is there anything else?)
+ echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+ exit 1
+ ;;
+esac
+
+# --- Finished
+exit 0
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/index b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/index
new file mode 100644
index 0000000..0d60dea
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/index
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/info/exclude b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/info/exclude
new file mode 100644
index 0000000..a5196d1
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/logs/HEAD b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/logs/HEAD
new file mode 100644
index 0000000..26d428e
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 107273e004e4f7ef07f13c83164d2cb2c51df65d Pablu <me@pablu.de> 1784924592 +0200 checkout: moving from main to FETCH_HEAD
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/00/readonly_5337796b0a74934b058105cc428c5616fd0f0d b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/00/readonly_5337796b0a74934b058105cc428c5616fd0f0d
new file mode 100644
index 0000000..33e7d1c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/00/readonly_5337796b0a74934b058105cc428c5616fd0f0d
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/0a/readonly_fc9295e33d59a441e3be5e0e642938ffbda0e0 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/0a/readonly_fc9295e33d59a441e3be5e0e642938ffbda0e0
new file mode 100644
index 0000000..1e4b2a1
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/0a/readonly_fc9295e33d59a441e3be5e0e642938ffbda0e0
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/10/readonly_7273e004e4f7ef07f13c83164d2cb2c51df65d b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/10/readonly_7273e004e4f7ef07f13c83164d2cb2c51df65d
new file mode 100644
index 0000000..5b3aea4
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/10/readonly_7273e004e4f7ef07f13c83164d2cb2c51df65d
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/16/readonly_d0b0db945ddf3d303278b53db08991f21ee859 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/16/readonly_d0b0db945ddf3d303278b53db08991f21ee859
new file mode 100644
index 0000000..390cb64
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/16/readonly_d0b0db945ddf3d303278b53db08991f21ee859
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/17/readonly_63803d3dbf5f7131e57db9486568c5fb744450 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/17/readonly_63803d3dbf5f7131e57db9486568c5fb744450
new file mode 100644
index 0000000..af4be11
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/17/readonly_63803d3dbf5f7131e57db9486568c5fb744450
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_4f4a2cc4bd8534e7df9f92f3fe62c91693463e b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_4f4a2cc4bd8534e7df9f92f3fe62c91693463e
new file mode 100644
index 0000000..cf16489
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_4f4a2cc4bd8534e7df9f92f3fe62c91693463e
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_52c3623b72f8cad4b4f68f961bc9a12df4fe84 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_52c3623b72f8cad4b4f68f961bc9a12df4fe84
new file mode 100644
index 0000000..dfa926d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/27/readonly_52c3623b72f8cad4b4f68f961bc9a12df4fe84
@@ -0,0 +1 @@
+xÍSÍŽ›0î™§°ÔkB€Óª=õÒ¨ªÊÆCpclêŸd£Õ¾{Ç&R6Z©Ro½„0£™ïg>„¶‚ô}÷ÎðòåkQX3„¬ÑÏéIˆpÜŒ3ø�|_¸2?rSëŸ~Gða(ŠÜ¢¼WÖø42ZÀpÀeQàû�3^Rûàl\òáù™T˜£(ÏÖ'mÏäå…¾*;˜°’"ÐTº:{pàqupŠâ—ÒÏ õFwò™û™ø‹ ü â¢ñ•‘(¢ ‘j�{nµ€��.ÊD,d—Ë>Àšw§qJ¢O->†¤òý�8ôX7ÀXÓí[Q ÆÆº¯ø®­;Æ%kAHV÷¬™:¹#ïÉ©É{Óº3êÞØ¦74œW>ÐÑ�Dç׈5á/\Gh0�…�dlšØN:^+ÿö¤–lïƒSO¥ÉzJ Öl–þ›#¬ºZâжC¾b¢=q¥éÄÿŽì{Ów��1:U%«Êê#ñ� )QÿÿG~gBQ,VF�îžÓ–Š·(¸ùâåÃê!Ä•¢ï�˜êºïw²m›¾®>A/»NJÞ1¨öL´ÝÄZ¾O�`e[²ë…߆b³í;á_ϸ}@½pâz‹MÎûÖŠ.E \ No newline at end of file
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_51026d393d97f55d261e1e20dbce0bb707b784 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_51026d393d97f55d261e1e20dbce0bb707b784
new file mode 100644
index 0000000..f5ce5f8
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_51026d393d97f55d261e1e20dbce0bb707b784
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_8209034800d64adf3487b3819bd6a531ae0606 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_8209034800d64adf3487b3819bd6a531ae0606
new file mode 100644
index 0000000..40d199f
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/37/readonly_8209034800d64adf3487b3819bd6a531ae0606
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3c/readonly_dd672a9627ea5b0f567efa2a860492622b1415 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3c/readonly_dd672a9627ea5b0f567efa2a860492622b1415
new file mode 100644
index 0000000..7aeda8d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3c/readonly_dd672a9627ea5b0f567efa2a860492622b1415
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3f/readonly_e8254ac9e3b167cd84e4c427d87f8af76e23ea b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3f/readonly_e8254ac9e3b167cd84e4c427d87f8af76e23ea
new file mode 100644
index 0000000..cb29c04
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/3f/readonly_e8254ac9e3b167cd84e4c427d87f8af76e23ea
@@ -0,0 +1 @@
+x…PKjÅ0 ìÚ§ÐÎN )í&èz€�ÄržÁ¿Úr¡”w÷Ú …·+I£™‘66xßžlØW 9”´#Ì p+‡<�Œ×AŒ=ðÞÉ«ÍÖœ1‘¸²)—­Ææø;¯Ø=«À‚FKÎxãV»Ô�¤-+ï»S¨¨¾Œ“Ú×·:tAýýHÿÒUÒ)N·éV'6D’©x2ãJ·)&Œè•htÿ#@JàƒÂ˜‡æLfj–~“Pð‡ªÌH%аkqov[ �W‹6s-éâw2Á‹îì¤:—üßfG=ôéîÒnŠyhqy>Ò¥‚W¼ê:}¥º÷ìÞ±_@$ŠË \ No newline at end of file
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/41/readonly_399a89bfca1a0095f7a53d1f89810861242e79 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/41/readonly_399a89bfca1a0095f7a53d1f89810861242e79
new file mode 100644
index 0000000..df88450
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/41/readonly_399a89bfca1a0095f7a53d1f89810861242e79
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/47/readonly_f6821b845d7069c2c68f7c7082be8e2c152b04 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/47/readonly_f6821b845d7069c2c68f7c7082be8e2c152b04
new file mode 100644
index 0000000..470206c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/47/readonly_f6821b845d7069c2c68f7c7082be8e2c152b04
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/4d/readonly_32363ad9bc23df7acbbd83598565e140168682 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/4d/readonly_32363ad9bc23df7acbbd83598565e140168682
new file mode 100644
index 0000000..fa1342d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/4d/readonly_32363ad9bc23df7acbbd83598565e140168682
@@ -0,0 +1,2 @@
+xu�±RÃ0D©õÛ%)¢†¨èhh¨Ò'rt¶o"ëÌIr†¿G²a†*�nönßn¤ÃãóÓÃñˆ‘Ôë1Í�s²qá s(Gt"9eu³5Uv9¡ç@¨oçÉ£ûÄ;I[¹�‘GúÙmZ)ÑC¶±–˜y¢Ùåq½ö–á©'Mp!à.zC\”>
++íw¿¡v‡‹5†{T#;ØÍû¼*ÎvsŽPÊE£¡èÍÿÚd-dZ¦×†°†á8 !”TG×J•�ê¹y€(8.r£J‹¾Äkf‰ß¥&W»šÄ—­·8®«—*Пy¬ùÜ4…ô \ No newline at end of file
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/51/readonly_1d507c6ba403c265e84aa3d6b5a66aef442953 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/51/readonly_1d507c6ba403c265e84aa3d6b5a66aef442953
new file mode 100644
index 0000000..4f5f5da
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/51/readonly_1d507c6ba403c265e84aa3d6b5a66aef442953
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/52/readonly_f002f346b87b770ebb3747d801684ff4d5f56a b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/52/readonly_f002f346b87b770ebb3747d801684ff4d5f56a
new file mode 100644
index 0000000..5751358
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/52/readonly_f002f346b87b770ebb3747d801684ff4d5f56a
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/56/readonly_ecca37040cae81738e7dc8c44e8e705d7f744c b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/56/readonly_ecca37040cae81738e7dc8c44e8e705d7f744c
new file mode 100644
index 0000000..2c6d3f6
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/56/readonly_ecca37040cae81738e7dc8c44e8e705d7f744c
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/6f/readonly_ecb9315d55b29f0f5f1a0efcc21b4ee10e6507 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/6f/readonly_ecb9315d55b29f0f5f1a0efcc21b4ee10e6507
new file mode 100644
index 0000000..d9c674a
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/6f/readonly_ecb9315d55b29f0f5f1a0efcc21b4ee10e6507
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7b/readonly_ecca042405c772eac6ec40f362327ad8e36693 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7b/readonly_ecca042405c772eac6ec40f362327ad8e36693
new file mode 100644
index 0000000..07a152a
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7b/readonly_ecca042405c772eac6ec40f362327ad8e36693
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7f/readonly_9cd53e889a0af1c8ac06d90a7f529692fb7082 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7f/readonly_9cd53e889a0af1c8ac06d90a7f529692fb7082
new file mode 100644
index 0000000..1988750
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/7f/readonly_9cd53e889a0af1c8ac06d90a7f529692fb7082
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8e/readonly_9f3e285bae904c5dad085e888395b10ceeab26 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8e/readonly_9f3e285bae904c5dad085e888395b10ceeab26
new file mode 100644
index 0000000..eca2d30
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8e/readonly_9f3e285bae904c5dad085e888395b10ceeab26
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8f/readonly_bc9e7a54d4cd045744e453334f71ee63780c03 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8f/readonly_bc9e7a54d4cd045744e453334f71ee63780c03
new file mode 100644
index 0000000..50f03f5
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/8f/readonly_bc9e7a54d4cd045744e453334f71ee63780c03
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/92/readonly_188bae7a779d87d71fb99f7953ed0bda60c6eb b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/92/readonly_188bae7a779d87d71fb99f7953ed0bda60c6eb
new file mode 100644
index 0000000..da37f6d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/92/readonly_188bae7a779d87d71fb99f7953ed0bda60c6eb
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/99/readonly_2c6604f456443ea726645083a1e33ab3ba3abc b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/99/readonly_2c6604f456443ea726645083a1e33ab3ba3abc
new file mode 100644
index 0000000..e5186ab
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/99/readonly_2c6604f456443ea726645083a1e33ab3ba3abc
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/9c/readonly_dbbb7b9d0d61f7384a4d05f12dc7e2075fba7c b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/9c/readonly_dbbb7b9d0d61f7384a4d05f12dc7e2075fba7c
new file mode 100644
index 0000000..401326d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/9c/readonly_dbbb7b9d0d61f7384a4d05f12dc7e2075fba7c
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a3/readonly_e4fc94c4ef9904a9c3dc43a7692439c8692023 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a3/readonly_e4fc94c4ef9904a9c3dc43a7692439c8692023
new file mode 100644
index 0000000..9b63bf5
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a3/readonly_e4fc94c4ef9904a9c3dc43a7692439c8692023
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a5/readonly_485ad8e3a1602c46b2e9811abd0452761c1e70 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a5/readonly_485ad8e3a1602c46b2e9811abd0452761c1e70
new file mode 100644
index 0000000..aa72db6
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/a5/readonly_485ad8e3a1602c46b2e9811abd0452761c1e70
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ae/readonly_cd3e093eccea0460991fdc918bdda0b58c5c11 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ae/readonly_cd3e093eccea0460991fdc918bdda0b58c5c11
new file mode 100644
index 0000000..d7f1b13
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ae/readonly_cd3e093eccea0460991fdc918bdda0b58c5c11
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/c8/readonly_312fd11e35846f0f2f2fb994b40fe1dc4cf948 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/c8/readonly_312fd11e35846f0f2f2fb994b40fe1dc4cf948
new file mode 100644
index 0000000..9c63a8c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/c8/readonly_312fd11e35846f0f2f2fb994b40fe1dc4cf948
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ca/readonly_d3716f06cd3b80418b5bd06b7ddcf844502a58 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ca/readonly_d3716f06cd3b80418b5bd06b7ddcf844502a58
new file mode 100644
index 0000000..18edeb5
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ca/readonly_d3716f06cd3b80418b5bd06b7ddcf844502a58
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d1/readonly_7b920f681623037f9c4d83442aae19716c0744 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d1/readonly_7b920f681623037f9c4d83442aae19716c0744
new file mode 100644
index 0000000..742e5aa
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d1/readonly_7b920f681623037f9c4d83442aae19716c0744
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d4/readonly_09b5ac3451619c127ef249a2d75e8e4f0693be b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d4/readonly_09b5ac3451619c127ef249a2d75e8e4f0693be
new file mode 100644
index 0000000..bd8b876
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d4/readonly_09b5ac3451619c127ef249a2d75e8e4f0693be
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d8/readonly_a3be96e59ca11039dd2bd5c482613e90fc0c76 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d8/readonly_a3be96e59ca11039dd2bd5c482613e90fc0c76
new file mode 100644
index 0000000..4cca567
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/d8/readonly_a3be96e59ca11039dd2bd5c482613e90fc0c76
@@ -0,0 +1,2 @@
+x…‘[K1…}m~Ű”Ò>¤k}hiU¤b±Ñ"BÉnÒ&˜Í.¹Tð×;I¡Þ܇0“M¾sN¦Ðu§ç'�«Õ†chwYáæ%V\Y\5sþ½¶ËÅõýôv6Ÿnæ³§u?²|ž-àr f¯*Búw«å˸hxá<”R”oÔI¡õ¡&$þ’Ö•ÚÂNy 7�µ»Q¾—�vt«´p@©°¶¶4˜ŠùR&b^)£ú ;ɹØç& õlÒŒÀKaà•´Z�.U̓μ@4bP—¢Œ9À*¹¬ƒÿ%3ŠD¡�Hä$L·G·h¡q_žrXêòx°�]–îo!qçð*¤Uò#!ƒN'6ñ1<¥R0Žéc|S7:씪`¹ZN“ŽwsWZÕøƒ�ŠéM •T�–�¡<,pk½Ø`º½Œ�oÃÀÌI æ/Q
+Ë-5lßw2û÷œN}ˆtô 0ü1ô�|E¼w \ No newline at end of file
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_c8ddd059a98b318cf0225cff3bb67ab6d66ac1 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_c8ddd059a98b318cf0225cff3bb67ab6d66ac1
new file mode 100644
index 0000000..285044d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_c8ddd059a98b318cf0225cff3bb67ab6d66ac1
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_ef3949f5d970786e78d88f1eb2687e68d3d476 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_ef3949f5d970786e78d88f1eb2687e68d3d476
new file mode 100644
index 0000000..c697993
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/dd/readonly_ef3949f5d970786e78d88f1eb2687e68d3d476
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/df/readonly_6d5aead0ff3e1663dd2e5e9f179ba88e886dc0 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/df/readonly_6d5aead0ff3e1663dd2e5e9f179ba88e886dc0
new file mode 100644
index 0000000..ffadd72
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/df/readonly_6d5aead0ff3e1663dd2e5e9f179ba88e886dc0
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/e2/readonly_a3be9442717a2d5028222e5310175c26bf7eb3 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/e2/readonly_a3be9442717a2d5028222e5310175c26bf7eb3
new file mode 100644
index 0000000..589eb50
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/e2/readonly_a3be9442717a2d5028222e5310175c26bf7eb3
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f3/readonly_5b8b893f1aa6d80da27731c2a685771d9a614f b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f3/readonly_5b8b893f1aa6d80da27731c2a685771d9a614f
new file mode 100644
index 0000000..855b148
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f3/readonly_5b8b893f1aa6d80da27731c2a685771d9a614f
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f8/readonly_8a80ddfe620609eed45752ddf4cf4cb3cd3ffd b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f8/readonly_8a80ddfe620609eed45752ddf4cf4cb3cd3ffd
new file mode 100644
index 0000000..441565c
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/f8/readonly_8a80ddfe620609eed45752ddf4cf4cb3cd3ffd
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/fd/readonly_e44002117933a436ae94729bd1133236ab9f6c b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/fd/readonly_e44002117933a436ae94729bd1133236ab9f6c
new file mode 100644
index 0000000..d995227
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/fd/readonly_e44002117933a436ae94729bd1133236ab9f6c
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ff/readonly_94999bc31a7ddc26674e3dbda98f68e0c133b6 b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ff/readonly_94999bc31a7ddc26674e3dbda98f68e0c133b6
new file mode 100644
index 0000000..92e6884
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/ff/readonly_94999bc31a7ddc26674e3dbda98f68e0c133b6
Binary files differ
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/info/.keep b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/info/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/info/.keep
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/pack/.keep b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/pack/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/objects/pack/.keep
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/heads/.keep b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/heads/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/heads/.keep
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/tags/.keep b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/tags/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/refs/tags/.keep
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/shallow b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/shallow
new file mode 100644
index 0000000..c367d86
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_git/shallow
@@ -0,0 +1 @@
+107273e004e4f7ef07f13c83164d2cb2c51df65d
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/bug_report.yml b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..8e9f3e2
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,100 @@
+name: Bug report
+description: Report something that isn't working as expected
+labels: [bug]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to file a bug report. Please fill in the
+ fields below so it can be reproduced.
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: A clear description of the bug.
+ validations:
+ required: true
+ - type: input
+ id: nvim-version
+ attributes:
+ label: Neovim version
+ description: Output of `nvim --version` (first line is enough).
+ placeholder: "NVIM v0.10.0"
+ validations:
+ required: true
+ - type: dropdown
+ id: plugin-manager
+ attributes:
+ label: Plugin manager
+ description: How is herdr-splits.nvim installed?
+ options:
+ - lazy.nvim
+ - packer.nvim
+ - None / manual (local path)
+ - Other
+ validations:
+ required: true
+ - type: input
+ id: herdr-version
+ attributes:
+ label: Herdr version
+ description: Output of `herdr --version`.
+ placeholder: "herdr 0.7.0"
+ validations:
+ required: true
+ - type: input
+ id: herdr-splits-version
+ attributes:
+ label: herdr-splits.nvim version
+ description: The plugin version / commit you're on (check with your plugin manager).
+ placeholder: "0.3.0"
+ validations:
+ required: false
+ - type: textarea
+ id: repro
+ attributes:
+ label: Reproduction steps
+ description: Minimal, numbered steps that trigger the bug.
+ placeholder: |
+ 1. Open Neovim inside a Herdr session (`HERDR_ENV=1`)
+ 2. Create two vertical splits
+ 3. Press `<C-l>`
+ 4. ...
+ validations:
+ required: true
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behaviour
+ validations:
+ required: true
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual behaviour
+ validations:
+ required: true
+ - type: textarea
+ id: checkhealth
+ attributes:
+ label: ":checkhealth herdr-splits output"
+ description: Paste the relevant output of `:checkhealth herdr-splits` (Neovim ≥ 0.10).
+ render: shell
+ validations:
+ required: false
+ - type: textarea
+ id: config
+ attributes:
+ label: Setup / config
+ description: Your `setup({})` call and any relevant keybinds.
+ render: lua
+ validations:
+ required: false
+ - type: textarea
+ id: os
+ attributes:
+ label: OS / terminal
+ description: Operating system and terminal emulator (note if Option-as-Alt is enabled on macOS).
+ placeholder: "macOS 14 / Ghostty (macos-option-as-alt = true)"
+ validations:
+ required: false
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/feature_request.yml b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..6fecb93
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,32 @@
+name: Feature request
+description: Suggest an idea or improvement for herdr-splits.nvim
+labels: [enhancement]
+body:
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem
+ description: What problem does this solve? What's currently hard or impossible to do?
+ validations:
+ required: true
+ - type: textarea
+ id: solution
+ attributes:
+ label: Proposed solution
+ description: What would you like herdr-splits.nvim to do instead?
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives considered
+ description: Any workarounds or other approaches you've tried.
+ validations:
+ required: false
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional context
+ description: Anything else relevant — links, screenshots, related config.
+ validations:
+ required: false
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/PULL_REQUEST_TEMPLATE.md b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..992c660
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,31 @@
+<!-- Thanks for the PR! Fill in the sections below. -->
+
+## Summary
+
+<!-- What does this change do, and why? One or two sentences. -->
+
+## Changes
+
+<!-- Bullet list of the notable changes. Mention any user-facing changes. -->
+
+-
+
+## Validation
+
+<!-- Check the automated commands you ran and describe any manual coverage. -->
+
+- [ ] `make test` passes
+- [ ] `make check-shell` passes
+- [ ] Plugin loads with no errors in a Neovim + Herdr session (`HERDR_ENV=1`)
+- [ ] `:checkhealth herdr-splits` reports no failures
+- [ ] Navigation (`<C-h/j/k/l>`) works across Neovim splits and Herdr panes
+- [ ] Resizing (`<M-h/j/k/l>`) works across Neovim splits and Herdr panes
+- [ ] Edge cases relevant to this change exercised (at_edge, count prefix,
+ floats, auto-unzoom)
+- [ ] `README.md` / `herdr-plugin.toml` updated (if user-facing)
+
+## Notes for review
+
+<!-- Anything reviewers should pay attention to, or follow-up work. -->
+
+Closes #NN
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/workflows/ci.yml b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/workflows/ci.yml
new file mode 100644
index 0000000..2752c36
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_github/workflows/ci.yml
@@ -0,0 +1,43 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ shell:
+ name: Bash syntax
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ persist-credentials: false
+ - run: make check-shell
+
+ test:
+ name: Neovim ${{ matrix.neovim-version }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ strategy:
+ fail-fast: false
+ matrix:
+ neovim-version: [v0.10.0, stable]
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ persist-credentials: false
+ submodules: true
+ - uses: rhysd/action-setup-vim@febef33995d6649302e9d88dda81e071b68f16a7 # v1.6.1
+ with:
+ neovim: true
+ version: ${{ matrix.neovim-version }}
+ - run: make test
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitignore b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitignore
new file mode 100644
index 0000000..f35b8b8
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitignore
@@ -0,0 +1 @@
+.pi-subagents/
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitmodules b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitmodules
new file mode 100644
index 0000000..8fbc9e7
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/dot_gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/mini.test"]
+ path = deps/mini.test
+ url = https://github.com/nvim-mini/mini.test.git
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/herdr-plugin.toml b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/herdr-plugin.toml
new file mode 100644
index 0000000..df6d5ae
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/herdr-plugin.toml
@@ -0,0 +1,62 @@
+id = "herdr-splits"
+name = "Herdr Splits"
+version = "0.5.1"
+min_herdr_version = "0.7.0"
+description = "Seamless navigation and resizing between Herdr panes and Neovim splits"
+platforms = ["linux", "macos"]
+
+# Herdr-side navigation actions. These check if the focused pane is running
+# Neovim and either forward the key chord or move Herdr pane focus.
+# Bind them in ~/.config/herdr/config.toml:
+# [[keys.command]]
+# key = "ctrl+h"
+# type = "plugin_action"
+# command = "herdr-splits.nav-left"
+
+[[actions]]
+id = "nav-left"
+title = "Navigate left (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-nav.sh", "left"]
+
+[[actions]]
+id = "nav-down"
+title = "Navigate down (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-nav.sh", "down"]
+
+[[actions]]
+id = "nav-up"
+title = "Navigate up (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-nav.sh", "up"]
+
+[[actions]]
+id = "nav-right"
+title = "Navigate right (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-nav.sh", "right"]
+
+[[actions]]
+id = "resize-left"
+title = "Resize left (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-resize.sh", "left"]
+
+[[actions]]
+id = "resize-down"
+title = "Resize down (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-resize.sh", "down"]
+
+[[actions]]
+id = "resize-up"
+title = "Resize up (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-resize.sh", "up"]
+
+[[actions]]
+id = "resize-right"
+title = "Resize right (Neovim/Herder)"
+contexts = ["global"]
+command = ["bash", "scripts/herdr-resize.sh", "right"]
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/conf.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/conf.lua
new file mode 100644
index 0000000..d17b920
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/conf.lua
@@ -0,0 +1,236 @@
+---herdr-splits shared config file (generated artifact) + notation translator.
+---Owns everything about `herdr-splits.conf`: path resolution, reading existing
+---managed values, translating Neovim key notation to Herdr chord notation,
+---and writing the resolved config back (atomically). Requires nothing else
+---from this plugin (avoiding a require-cycle with config.lua).
+---@class HerdrSplitsConf
+local M = {}
+
+-- Neovim modifier prefix -> Herdr modifier name.
+local MOD = { C = 'ctrl', M = 'alt', A = 'alt', S = 'shift', D = 'cmd' }
+-- Herdr's documented specials: lowercase the terminal key when present.
+local SPECIAL = {
+ left = 'left', right = 'right', up = 'up', down = 'down',
+ enter = 'enter', ['return'] = 'enter', tab = 'tab',
+ esc = 'esc', escape = 'esc', space = 'space', backspace = 'backspace',
+ del = 'delete', delete = 'delete', home = 'home',
+ ['end'] = 'end', pageup = 'pageup', pagedown = 'pagedown',
+}
+
+-- Marker line stamped into every generated conf. Its presence means the file
+-- is the plugin's own output: setup() is authoritative, so read_managed() does
+-- NOT adopt it back (removing an opt reverts to the default on the next start).
+local MARKER = '# herdr-splits-generated-v1'
+
+local uv = vim.uv or vim.loop
+
+-- Lazily-resolved cache of the shared config path, so setup() shells out to
+-- `herdr plugin config-dir` at most once per process.
+local resolved_path
+
+---Resolve the herdr binary: a config.herdr_bin override (when the config
+---module is loaded), then HERDR_BIN_PATH, then 'herdr'. pcall-required so
+---conf.lua never hard-requires config.lua at load time (cycle-safe).
+---@return string
+local function resolve_herdr_bin()
+ local ok, cfg = pcall(require, 'herdr-splits.config')
+ if ok and cfg.herdr_bin then return cfg.herdr_bin end
+ return vim.env.HERDR_BIN_PATH or 'herdr'
+end
+
+---Best-effort `herdr plugin config-dir herdr-splits` query. Returns the
+---directory printed by herdr (trimmed), or nil on any failure / empty output.
+---@return string|nil
+local function query_config_dir()
+ local ok, obj = pcall(vim.system, { resolve_herdr_bin(), 'plugin', 'config-dir', 'herdr-splits' }, { text = true })
+ if not ok or not obj then return nil end
+ local wok, res = pcall(obj.wait, obj)
+ if not wok or not res or res.code ~= 0 then return nil end
+ local out = (res.stdout or ''):gsub('^%s+', ''):gsub('%s+$', '')
+ return out ~= '' and out or nil
+end
+
+---Path to the shared herdr-splits config file (same file the Herdr-side
+---scripts read). Precedence: (1) HERDR_SPLITS_CONFIG; (2) HERDR_PLUGIN_CONFIG_DIR
+---+ `/herdr-splits.conf`; (3) `herdr plugin config-dir herdr-splits` (via the
+---herdr binary) + `/herdr-splits.conf`; (4) XDG_CONFIG_HOME / ~/.config
+---fallback. Resolved once and cached, so setup() shells out at most once.
+---@return string
+function M.path()
+ if resolved_path then return resolved_path end
+ if vim.env.HERDR_SPLITS_CONFIG and vim.env.HERDR_SPLITS_CONFIG ~= '' then
+ resolved_path = vim.env.HERDR_SPLITS_CONFIG
+ return resolved_path
+ end
+ if vim.env.HERDR_PLUGIN_CONFIG_DIR and vim.env.HERDR_PLUGIN_CONFIG_DIR ~= '' then
+ resolved_path = vim.env.HERDR_PLUGIN_CONFIG_DIR .. '/herdr-splits.conf'
+ return resolved_path
+ end
+ local dir = query_config_dir()
+ if dir then
+ resolved_path = dir .. '/herdr-splits.conf'
+ return resolved_path
+ end
+ local xdg = vim.env.XDG_CONFIG_HOME
+ local base = (xdg and xdg:sub(1, 1) == '/') and xdg
+ or ((vim.env.HOME or '~') .. '/.config')
+ resolved_path = base .. '/herdr/plugins/config/herdr-splits/herdr-splits.conf'
+ return resolved_path
+end
+
+---Translate a Neovim key notation (e.g. `<C-h>`, `<M-Left>`, `h`) into the
+---Herdr chord notation the scripts and `herdr pane send-keys` expect
+---(e.g. `ctrl+h`, `alt+left`, `h`). Returns nil for modifier-only / empty
+---input so callers can fall back to a per-direction default.
+---@param nvim_key string
+---@return string|nil
+function M.to_herdr(nvim_key)
+ if type(nvim_key) ~= 'string' or nvim_key == '' then return nil end
+ if not nvim_key:match('^<.+>$') then return nvim_key:lower() end -- plain "h" -> "h"
+ local inner = nvim_key:sub(2, -2)
+ local mods, key = {}, nil
+ for part in inner:gmatch('[^-]+') do
+ local m = MOD[part:upper()]
+ if m then
+ mods[#mods + 1] = m
+ else
+ key = SPECIAL[part:lower()] or part:lower()
+ end
+ end
+ if not key then return nil end
+ mods[#mods + 1] = key
+ return table.concat(mods, '+')
+end
+
+---Parse the shared config file for the managed keys only. When the file
+---carries the generated MARKER it is the plugin's own output and is NOT read
+---back (setup() is authoritative — removing an opt reverts to default). When
+---the MARKER is absent (a legacy/hand-edited conf) the managed values are
+---parsed and adopted once; the second return value is true only when at
+---least one managed value was found, so the caller can emit a one-time
+---migration notice. Returns `({}, false)` for a missing/marker-tagged file;
+---last occurrence wins (matches the scripts' `tail -n 1`).
+---@return table values, boolean adopted
+function M.read_managed()
+ local f = io.open(M.path(), 'r')
+ if not f then return {}, false end
+ local out = { nav_keys = {}, resize_keys = {} }
+ for line in f:lines() do
+ -- Generated marker anywhere => our own output; do not adopt it back.
+ if line:match('^%s*' .. vim.pesc(MARKER) .. '%s*$') then
+ f:close()
+ return {}, false
+ end
+ local k, v = line:match('^%s*([%w_]+)%s*=%s*([^%s#]+)')
+ if k and v then
+ local dir = k:match('^nav_key_(%a+)$')
+ if dir then
+ out.nav_keys[dir] = v
+ else
+ dir = k:match('^resize_key_(%a+)$')
+ if dir then
+ out.resize_keys[dir] = v
+ elseif k == 'unzoom_on_nav' then
+ out.unzoom_on_nav = (v ~= 'false')
+ elseif k == 'nav_at_edge' then
+ out.nav_at_edge = (v == 'stop' and 'stop' or 'wrap')
+ end
+ end
+ end
+ end
+ f:close()
+ local adopted = false
+ for _, t in pairs({ out.nav_keys, out.resize_keys }) do
+ for _ in pairs(t) do adopted = true; break end
+ end
+ if out.unzoom_on_nav ~= nil or out.nav_at_edge ~= nil then adopted = true end
+ return out, adopted
+end
+
+---Write the full managed key set to the shared config with a generated-file
+---header (including the MARKER), atomically via a unique same-dir temp +
+---os.rename so a script never observes a partial write and two concurrent
+---Neovim instances don't share a temp inode. Skips the write entirely when the
+---content is unchanged (preserves mtime, reduces multi-instance contention).
+---Returns (true, nil) on success or (false, err) on failure; callers wrap in
+---pcall so a write failure never crashes startup.
+---@param resolved table resolved managed config (nav_keys, resize_keys, unzoom_on_nav, nav_at_edge)
+---@return boolean ok, string|nil err
+function M.write(resolved)
+ local path = M.path()
+ local lines = {
+ MARKER,
+ '# herdr-splits plugin config — GENERATED by herdr-splits.nvim setup().',
+ '# Regenerated from defaults + setup() opts on every setup(); do not edit by hand.',
+ '# A headerless/legacy conf is adopted once on migration (see the startup',
+ '# notice), then overwritten thereafter — remove an opt in setup() to revert.',
+ 'nav_key_left=' .. resolved.nav_keys.left,
+ 'nav_key_down=' .. resolved.nav_keys.down,
+ 'nav_key_up=' .. resolved.nav_keys.up,
+ 'nav_key_right=' .. resolved.nav_keys.right,
+ 'resize_key_left=' .. resolved.resize_keys.left,
+ 'resize_key_down=' .. resolved.resize_keys.down,
+ 'resize_key_up=' .. resolved.resize_keys.up,
+ 'resize_key_right=' .. resolved.resize_keys.right,
+ 'unzoom_on_nav=' .. (resolved.unzoom_on_nav == false and 'false' or 'true'),
+ 'nav_at_edge=' .. (resolved.nav_at_edge == 'stop' and 'stop' or 'wrap'),
+ }
+ local content = table.concat(lines, '\n') .. '\n'
+
+ -- Skip when unchanged: only when the existing file's bytes match exactly.
+ local existing = io.open(path, 'r')
+ if existing then
+ local prev = existing:read('*a')
+ existing:close()
+ if prev == content then return true end
+ end
+
+ vim.fn.mkdir(vim.fn.fnamemodify(path, ':h'), 'p')
+ -- Unique same-dir temp so os.rename stays same-filesystem (atomic) and two
+ -- Neovim instances don't collide on a shared temp inode.
+ local tmp = path .. '.tmp.' .. vim.fn.getpid() .. '.' .. tostring(uv.hrtime())
+ local f, err = io.open(tmp, 'w')
+ if not f then
+ pcall(os.remove, tmp)
+ vim.notify('herdr-splits: failed to open conf temp file: ' .. tostring(err), vim.log.levels.WARN)
+ return false, err
+ end
+ local wok, werr = f:write(content)
+ -- file:close() flushes buffered output and can itself fail with a delayed
+ -- write error (disk full, NFS, quota). Its result MUST be checked before
+ -- os.rename: otherwise an incomplete temp replaces the live config and the
+ -- atomic-write guarantee is lost. LuaJIT returns true on success or
+ -- (nil, err) on a flush failure — same form as file:write, checked here too.
+ local cok, cerr = f:close()
+ if (not wok) or (not cok) then
+ local why = (not wok) and werr or cerr
+ pcall(os.remove, tmp)
+ vim.notify('herdr-splits: failed to write conf temp file: ' .. tostring(why), vim.log.levels.WARN)
+ return false, why
+ end
+ local ok, rerr = os.rename(tmp, path)
+ if not ok then
+ pcall(os.remove, tmp)
+ vim.notify('herdr-splits: failed to write conf: ' .. tostring(rerr), vim.log.levels.WARN)
+ end
+ return ok, rerr
+end
+
+---Runnable self-check for the notation translator. Asserts on the documented
+---mapping table; returns true on success. Intended for headless validation:
+---`nvim --headless -c "lua assert(require('herdr-splits.conf')._selfcheck())" -c "qa"`.
+---@return boolean
+function M._selfcheck()
+ local cases = {
+ { '<C-h>', 'ctrl+h' }, { '<C-j>', 'ctrl+j' }, { '<C-k>', 'ctrl+k' }, { '<C-l>', 'ctrl+l' },
+ { '<M-h>', 'alt+h' }, { '<M-Left>', 'alt+left' }, { '<S-Left>', 'shift+left' },
+ { '<D-x>', 'cmd+x' }, { '<C-M-h>', 'ctrl+alt+h' }, { 'h', 'h' }, { '<Esc>', 'esc' },
+ }
+ for _, c in ipairs(cases) do
+ local got = M.to_herdr(c[1])
+ assert(got == c[2], ('to_herdr(%q)=%q want %q'):format(c[1], tostring(got), c[2]))
+ end
+ return true
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/config.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/config.lua
new file mode 100644
index 0000000..1763803
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/config.lua
@@ -0,0 +1,146 @@
+---@class HerdrSplitsConfig
+---@field default_amount number Resize amount as Herdr ratio (float, e.g. 0.03 = 3%)
+---@field neovim_amount number Resize amount for native Neovim resizes (integer cells, default 3)
+---@field at_edge 'wrap'|'stop'|'split'|function Neovim window-edge behavior (distinct from nav_at_edge below)
+---@field ignored_buftypes string[] Buffer types ignored during resize
+---@field ignored_filetypes string[] Filetypes ignored during resize
+---@field move_cursor_same_row boolean Keep cursor on same screen row when moving left/right
+---@field herdr_bin string|nil Path to herdr binary (auto-detected if nil)
+---@field ignored_events string[] Autocmd events to ignore during resize operations
+---@field auto_sync_herdr boolean|nil If true, auto-sync the Herdr-managed checkout to match this lazy commit (opt-in; default false)
+---@field floating_zindex_max number Threshold below which a floating window's zindex classifies it as an embedded sidebar (default 50; Neovim's default float zindex)
+---@field ignore_previewwindows boolean If true, vim.wo[winid].previewwindow windows (e.g. dadbod `.dbout`) are treated as sidebars (opt-in; default false)
+--- Managed config (written to herdr-splits.conf by setup()). Pass Neovim
+--- notation (e.g. `<C-h>`, `<M-Left>`); stored/written in Herdr notation.
+---@field nav_keys table<string,string> Forward chords per direction: {left,down,up,right} (Neovim notation in opts; Herdr notation internally)
+---@field resize_keys table<string,string> Resize forward chords per direction: {left,down,up,right} (Neovim notation in opts; Herdr notation internally)
+---@field unzoom_on_nav boolean If true, auto-unzoom when navigating away from a zoomed pane (default true)
+---@field nav_at_edge 'wrap'|'stop' Herdr pane-boundary wrap behavior (distinct from at_edge, which is the Neovim window-edge behavior)
+
+-- Managed config defaults (Herdr notation). The single source of truth for
+-- the key set written to herdr-splits.conf; deep-copied into M below and into
+-- the setup() merge base so the two cannot drift.
+local managed_defaults = {
+ nav_keys = { left = 'ctrl+h', down = 'ctrl+j', up = 'ctrl+k', right = 'ctrl+l' },
+ resize_keys = { left = 'alt+h', down = 'alt+j', up = 'alt+k', right = 'alt+l' },
+ unzoom_on_nav = true,
+ nav_at_edge = 'wrap',
+}
+
+local M = {
+ default_amount = 0.03,
+ neovim_amount = 3,
+ at_edge = 'wrap',
+ ignored_buftypes = { 'nofile', 'quickfix', 'prompt', 'help', 'terminal' },
+ ignored_filetypes = {
+ 'NvimTree',
+ -- sidebars
+ 'neo-tree',
+ 'snacks_dashboard',
+ 'snacks_explorer',
+ 'snacks_picker',
+ -- DB / REPL / data sidebars
+ 'dadbod-ui',
+ 'dbout',
+ -- outlines / symbols
+ 'aerial',
+ 'Outline',
+ -- diagnostics / quick lists
+ 'Trouble',
+ 'quickfix',
+ },
+ move_cursor_same_row = false,
+ herdr_bin = nil,
+ ignored_events = { 'BufEnter', 'WinEnter' },
+ auto_sync_herdr = false,
+ floating_zindex_max = 50,
+ ignore_previewwindows = false,
+ -- Managed config: deep-copied from managed_defaults (single source).
+ -- Stored in Herdr chord notation; setup() translates user Neovim notation
+ -- and writes these to herdr-splits.conf for the scripts.
+ nav_keys = vim.deepcopy(managed_defaults.nav_keys),
+ resize_keys = vim.deepcopy(managed_defaults.resize_keys),
+ unzoom_on_nav = managed_defaults.unzoom_on_nav,
+ nav_at_edge = managed_defaults.nav_at_edge,
+}
+
+local conf = require('herdr-splits.conf')
+
+---Apply user configuration on top of defaults and publish the managed keys
+---to the shared `herdr-splits.conf` (so the Herdr-side scripts agree).
+---Idempotent in outcome: managed keys (nav_keys/resize_keys/unzoom_on_nav/nav_at_edge)
+---rebuild from defaults + opts each call; non-managed opts merge into existing config.
+---
+---Managed keys (nav_keys/resize_keys/unzoom_on_nav/nav_at_edge) resolve with
+---precedence: defaults -> existing conf value (adopt-once) -> explicit opt.
+---User chords are given in Neovim notation and translated to Herdr notation
+---before merging; the resolved set is written to the conf atomically (write
+---failures never crash startup).
+---@param opts table|nil
+function M.setup(opts)
+ opts = opts or {}
+
+ -- Apply herdr_bin from opts before the first conf.path() resolution so the
+ -- documented precedence (config.herdr_bin -> HERDR_BIN_PATH -> 'herdr') holds.
+ if opts.herdr_bin ~= nil then M.herdr_bin = opts.herdr_bin end
+
+ -- Managed opts: translate user chords nvim -> herdr, capture only what was passed.
+ local user = {}
+ if opts.nav_keys then
+ user.nav_keys = {}
+ for dir, k in pairs(opts.nav_keys) do user.nav_keys[dir] = conf.to_herdr(k) end
+ end
+ if opts.resize_keys then
+ user.resize_keys = {}
+ for dir, k in pairs(opts.resize_keys) do user.resize_keys[dir] = conf.to_herdr(k) end
+ end
+ if opts.unzoom_on_nav ~= nil then user.unzoom_on_nav = opts.unzoom_on_nav end
+ if opts.nav_at_edge ~= nil then user.nav_at_edge = opts.nav_at_edge end
+
+ -- Precedence: defaults -> legacy/headerless conf (adopt once) -> explicit
+ -- user opts. read_managed returns {} (no adoption) once the conf carries
+ -- our generated marker, so removing an opt reverts to the default next start.
+ local existing, migrated = conf.read_managed()
+ local resolved = vim.deepcopy(managed_defaults)
+ resolved = vim.tbl_deep_extend('force', resolved, existing)
+ resolved = vim.tbl_deep_extend('force', resolved, user)
+
+ M.nav_keys, M.resize_keys = resolved.nav_keys, resolved.resize_keys
+ M.unzoom_on_nav, M.nav_at_edge = resolved.unzoom_on_nav, resolved.nav_at_edge
+
+ -- One-time migration notice: a headerless/legacy conf was adopted. Fires
+ -- once — after this setup() the conf carries the marker, so later setups
+ -- don't adopt. Tell the user to move these into setup() to persist them.
+ if migrated then
+ local parts = {}
+ for _, kind in ipairs({ 'nav_keys', 'resize_keys' }) do
+ for dir, v in pairs(existing[kind] or {}) do
+ parts[#parts + 1] = ('%s.%s=%s'):format(kind, dir, v)
+ end
+ end
+ if existing.unzoom_on_nav ~= nil then
+ parts[#parts + 1] = 'unzoom_on_nav=' .. tostring(existing.unzoom_on_nav)
+ end
+ if existing.nav_at_edge ~= nil then
+ parts[#parts + 1] = 'nav_at_edge=' .. existing.nav_at_edge
+ end
+ vim.notify(
+ 'herdr-splits: adopted legacy conf values (' .. table.concat(parts, ', ')
+ .. '). Add them to setup() (Neovim notation) to keep them — the conf is '
+ .. 'now regenerated on every setup().',
+ vim.log.levels.INFO
+ )
+ end
+
+ -- Non-managed opts (default_amount, at_edge, ignored_filetypes, …) merge as before.
+ local other = vim.deepcopy(opts)
+ other.nav_keys, other.resize_keys = nil, nil
+ other.unzoom_on_nav, other.nav_at_edge = nil, nil
+ local merged = vim.tbl_deep_extend('force', M, other)
+ for k, v in pairs(merged) do M[k] = v end
+
+ -- Publish to the shared conf for the Herdr scripts (never fatal).
+ pcall(conf.write, resolved)
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/health.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/health.lua
new file mode 100644
index 0000000..3751026
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/health.lua
@@ -0,0 +1,46 @@
+---:checkhealth herdr-splits integration.
+---Discovered automatically by Neovim's built-in `:checkhealth` via the
+---runtimefile lookup in `lua/<plugin>/health.lua`.
+---Requires Neovim ≥ 0.10 for `vim.health`.
+
+local M = {}
+
+function M.check()
+ local config = require('herdr-splits.config')
+ local herdr = require('herdr-splits.herdr')
+ local win = require('herdr-splits.win')
+
+ vim.health.start('herdr-splits')
+
+ if herdr.is_in_session() then
+ vim.health.ok(
+ string.format(
+ 'Running inside a Herdr session (HERDR_ENV=1, HERDR_PANE_ID=%s)',
+ tostring(vim.env.HERDR_PANE_ID)
+ )
+ )
+ else
+ vim.health.warn(
+ 'Not inside a Herdr session (HERDR_ENV='
+ .. tostring(vim.env.HERDR_ENV)
+ .. ') — nav/resize will not cross pane boundaries.'
+ )
+ end
+
+ vim.health.info(string.format('ignored_buftypes: %s', vim.inspect(config.ignored_buftypes)))
+ vim.health.info(string.format('ignored_filetypes: %s', vim.inspect(config.ignored_filetypes)))
+ vim.health.info(string.format('floating_zindex_max: %s', tostring(config.floating_zindex_max)))
+ vim.health.info(string.format('ignore_previewwindows: %s', tostring(config.ignore_previewwindows)))
+
+ if win.is_embedded_floating_window() then
+ vim.health.warn('Current window is an embedded floating window (zindex < 50) — treat as sidebar.')
+ end
+ if win.is_ignored_win() then
+ vim.health.info('Current window matches an ignored filetype/buftype.')
+ end
+ if win.is_floating() and not win.is_embedded_floating_window() then
+ vim.health.info('Current window is a true floating popup — nav/resize will forward to Herdr.')
+ end
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/herdr.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/herdr.lua
new file mode 100644
index 0000000..a5485ad
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/herdr.lua
@@ -0,0 +1,160 @@
+---Herdr CLI wrapper. All Herdr subprocess calls go through this module.
+---@class HerdrSplitsHerdr
+local M = {}
+
+local config = require('herdr-splits.config')
+
+---Resolve the herdr binary path.
+---@return string
+function M.herdr_bin()
+ return config.herdr_bin or vim.env.HERDR_BIN_PATH or 'herdr'
+end
+
+---Check if Neovim is running inside a Herdr session.
+---@return boolean
+function M.is_in_session()
+ return vim.env.HERDR_ENV == '1' and vim.env.HERDR_PANE_ID ~= nil and #vim.env.HERDR_PANE_ID > 0
+end
+
+---Get the current Herdr pane ID.
+---@return string|nil
+function M.current_pane_id()
+ if not M.is_in_session() then
+ return nil
+ end
+ return vim.env.HERDR_PANE_ID
+end
+
+---Run a herdr CLI command and return stdout, stderr, and exit code.
+---@param args string[]
+---@return string stdout, string stderr, number exit_code
+local function herdr_exec(args)
+ local cmd = vim.list_extend({ M.herdr_bin() }, args, 1, #args)
+ local obj = vim.system(cmd, { text = true }):wait()
+ return obj.stdout, obj.stderr, obj.code
+end
+
+---Check if the current Herdr pane is at the layout boundary in the given direction.
+---Calls `herdr pane edges --current` and parses the JSON response.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@return boolean|nil true if at edge, false if neighbor exists, nil on error
+function M.current_pane_at_edge(direction)
+ if not M.is_in_session() then
+ return nil
+ end
+
+ local edge_key = direction
+ local stdout, _, code = herdr_exec({ 'pane', 'edges', '--current' })
+
+ if code ~= 0 or not stdout or #stdout == 0 then
+ return nil
+ end
+
+ local ok, data = pcall(vim.json.decode, stdout)
+ if
+ not ok
+ or type(data) ~= 'table'
+ or type(data.result) ~= 'table'
+ or type(data.result.edges) ~= 'table'
+ then
+ return nil
+ end
+
+ local at_edge = data.result.edges[edge_key]
+ if type(at_edge) ~= 'boolean' then
+ return nil
+ end
+ return at_edge
+end
+
+---Check if the current Herdr pane is zoomed.
+---Calls `herdr pane layout --current` and parses JSON.
+---@return boolean|nil true if zoomed, false if not, nil on error
+function M.current_pane_is_zoomed()
+ if not M.is_in_session() then
+ return nil
+ end
+
+ local stdout, _, code = herdr_exec({ 'pane', 'layout', '--current' })
+
+ if code ~= 0 or not stdout or #stdout == 0 then
+ return nil
+ end
+
+ local ok, data = pcall(vim.json.decode, stdout)
+ if
+ not ok
+ or type(data) ~= 'table'
+ or type(data.result) ~= 'table'
+ or type(data.result.layout) ~= 'table'
+ then
+ return nil
+ end
+
+ local zoomed = data.result.layout.zoomed
+ if type(zoomed) ~= 'boolean' then
+ return nil
+ end
+ return zoomed
+end
+
+---Focus a Herdr pane in the given direction.
+---Calls `herdr pane focus --direction <dir> --current`.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@return boolean true on success
+function M.focus_pane(direction)
+ if not M.is_in_session() then
+ return false
+ end
+
+ local _, _, code = herdr_exec({ 'pane', 'focus', '--direction', direction, '--current' })
+ return code == 0
+end
+
+---Unzoom the current Herdr pane (turn off zoom).
+---Calls `herdr pane zoom --off --current`.
+---@return boolean true on success
+function M.unzoom()
+ if not M.is_in_session() then
+ return false
+ end
+
+ local _, _, code = herdr_exec({ 'pane', 'zoom', '--off', '--current' })
+ return code == 0
+end
+
+---Check whether auto-unzoom is enabled.
+---Reads `config.unzoom_on_nav` (resolved from setup(); default: enabled).
+---@return boolean
+function M.unzoom_enabled()
+ return config.unzoom_on_nav ~= false
+end
+
+---Edge-wrap behavior across the Herdr pane boundary, read from the resolved
+---in-memory config (the same value written to the shared conf that the
+---Herdr-side scripts read). `stop` suppresses wrap-across-boundary on both
+---the plain-pane side (handled by the Herdr script) and the Neovim
+---edge-wrap side (handled here). Anything else defaults to `wrap`.
+---@return '"wrap"'|'"stop"'
+function M.nav_at_edge()
+ return config.nav_at_edge == 'stop' and 'stop' or 'wrap'
+end
+
+---Resize the current Herdr pane in the given direction.
+---Calls `herdr pane resize --direction <dir> --amount <float> --current`.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@param amount number Float ratio (e.g., 0.03 = 3% of terminal dimension)
+---@return boolean true on success
+function M.resize_pane(direction, amount)
+ if not M.is_in_session() then
+ return false
+ end
+
+ local amount_str = string.format('%.4f', amount)
+ local _, _, code = herdr_exec({
+ 'pane', 'resize', '--direction', direction, '--amount', amount_str, '--current',
+ })
+ return code == 0
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/init.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/init.lua
new file mode 100644
index 0000000..3782090
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/init.lua
@@ -0,0 +1,162 @@
+---herdr-splits.nvim — Seamless navigation and resizing between Neovim splits
+---and Herdr panes. Makes Herdr terminal splits behave like native Neovim windows.
+---
+---Usage:
+--- require('herdr-splits').setup({ ... })
+--- vim.keymap.set('n', '<C-h>', require('herdr-splits').move_cursor_left)
+--- vim.keymap.set('n', '<M-h>', require('herdr-splits').resize_left)
+
+local config = require('herdr-splits.config')
+local nav = require('herdr-splits.nav')
+local resize_mod = require('herdr-splits.resize')
+local win = require('herdr-splits.win')
+local sync = require('herdr-splits.sync')
+
+local M = {}
+
+---Apply configuration. Idempotent — calling again merges into existing config.
+---@param opts table|nil Configuration options (see config.lua for defaults)
+function M.setup(opts)
+ config.setup(opts)
+ pcall(sync.sync)
+end
+
+---Sync the Herdr-managed checkout to match this lazy commit.
+---Intended as a lazy.nvim `build` hook entry point; respects `auto_sync_herdr`.
+---Safe to call at any time; no-op unless opted in.
+function M.sync_herdr()
+ pcall(sync.sync)
+end
+
+---Resize the current split to the left.
+---Amount defaults to config.default_amount. Multiplied by vim.v.count1.
+---@param amount number|nil
+function M.resize_left(amount)
+ local eventignore_orig = vim.o.eventignore
+ vim.o.eventignore = table.concat(config.ignored_events, ',')
+ resize_mod.is_resizing = true
+ local cur_win = vim.api.nvim_get_current_win()
+ local ok, err = pcall(resize_mod.resize, 'left', amount)
+ if not ok then
+ vim.notify('herdr-splits: resize_left failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+ pcall(vim.api.nvim_set_current_win, cur_win)
+ resize_mod.is_resizing = false
+ vim.o.eventignore = eventignore_orig
+end
+
+---Resize the current split downward.
+---@param amount number|nil
+function M.resize_down(amount)
+ local eventignore_orig = vim.o.eventignore
+ vim.o.eventignore = table.concat(config.ignored_events, ',')
+ resize_mod.is_resizing = true
+ local cur_win = vim.api.nvim_get_current_win()
+ local ok, err = pcall(resize_mod.resize, 'down', amount)
+ if not ok then
+ vim.notify('herdr-splits: resize_down failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+ pcall(vim.api.nvim_set_current_win, cur_win)
+ resize_mod.is_resizing = false
+ vim.o.eventignore = eventignore_orig
+end
+
+---Resize the current split upward.
+---@param amount number|nil
+function M.resize_up(amount)
+ local eventignore_orig = vim.o.eventignore
+ vim.o.eventignore = table.concat(config.ignored_events, ',')
+ resize_mod.is_resizing = true
+ local cur_win = vim.api.nvim_get_current_win()
+ local ok, err = pcall(resize_mod.resize, 'up', amount)
+ if not ok then
+ vim.notify('herdr-splits: resize_up failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+ pcall(vim.api.nvim_set_current_win, cur_win)
+ resize_mod.is_resizing = false
+ vim.o.eventignore = eventignore_orig
+end
+
+---Resize the current split to the right.
+---@param amount number|nil
+function M.resize_right(amount)
+ local eventignore_orig = vim.o.eventignore
+ vim.o.eventignore = table.concat(config.ignored_events, ',')
+ resize_mod.is_resizing = true
+ local cur_win = vim.api.nvim_get_current_win()
+ local ok, err = pcall(resize_mod.resize, 'right', amount)
+ if not ok then
+ vim.notify('herdr-splits: resize_right failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+ pcall(vim.api.nvim_set_current_win, cur_win)
+ resize_mod.is_resizing = false
+ vim.o.eventignore = eventignore_orig
+end
+
+---Move cursor to the left, crossing into Herdr pane if at Neovim edge.
+---@param opts table|nil { same_row: boolean|nil, at_edge: string|function|nil }
+function M.move_cursor_left(opts)
+ resize_mod.is_resizing = false
+ local ok, err = pcall(nav.move_cursor, 'left', opts)
+ if not ok then
+ vim.notify('herdr-splits: move_cursor_left failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+end
+
+---Move cursor downward, crossing into Herdr pane if at Neovim edge.
+---@param opts table|nil
+function M.move_cursor_down(opts)
+ resize_mod.is_resizing = false
+ local ok, err = pcall(nav.move_cursor, 'down', opts)
+ if not ok then
+ vim.notify('herdr-splits: move_cursor_down failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+end
+
+---Move cursor upward, crossing into Herdr pane if at Neovim edge.
+---@param opts table|nil
+function M.move_cursor_up(opts)
+ resize_mod.is_resizing = false
+ local ok, err = pcall(nav.move_cursor, 'up', opts)
+ if not ok then
+ vim.notify('herdr-splits: move_cursor_up failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+end
+
+---Move cursor to the right, crossing into Herdr pane if at Neovim edge.
+---@param opts table|nil
+function M.move_cursor_right(opts)
+ resize_mod.is_resizing = false
+ local ok, err = pcall(nav.move_cursor, 'right', opts)
+ if not ok then
+ vim.notify('herdr-splits: move_cursor_right failed: ' .. tostring(err), vim.log.levels.ERROR)
+ end
+end
+
+---Append a filetype to ignored_filetypes at runtime (de-duped).
+---@param name string Filetype name, e.g. 'fugitive'
+---@return boolean true if added, false if already present
+function M.add_ignored_filetype(name)
+ for _, v in ipairs(config.ignored_filetypes) do
+ if v == name then
+ return false
+ end
+ end
+ table.insert(config.ignored_filetypes, name)
+ return true
+end
+
+---Append a buftype to ignored_buftypes at runtime (de-duped).
+---@param name string Buffer type, e.g. 'help'
+---@return boolean true if added, false if already present
+function M.add_ignored_buftype(name)
+ for _, v in ipairs(config.ignored_buftypes) do
+ if v == name then
+ return false
+ end
+ end
+ table.insert(config.ignored_buftypes, name)
+ return true
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua
new file mode 100644
index 0000000..56ecca3
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua
@@ -0,0 +1,206 @@
+---Navigation logic: seamless movement between Neovim splits and Herdr panes.
+---@class HerdrSplitsNav
+local M = {}
+
+local config = require('herdr-splits.config')
+local herdr = require('herdr-splits.herdr')
+local win = require('herdr-splits.win')
+
+---Treat the current window as a sidebar that should not be navigated through.
+---Combines the configured ignore lists with the embedded-float heuristic.
+---@return boolean
+local function is_sidebar()
+ return win.is_ignored_or_preview() or win.is_embedded_floating_window()
+end
+
+---Split a new Neovim window using the user's placement preferences.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+local function split_edge(direction)
+ if direction == 'left' or direction == 'right' then
+ vim.cmd('vsp')
+ else
+ vim.cmd('sp')
+ end
+end
+
+---Move cursor between Neovim splits, falling through to Herdr at edges.
+---This is the core navigation function.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@param opts table|nil { same_row: boolean|nil, at_edge: string|function|nil }
+function M.move_cursor(direction, opts)
+ local same_row = config.move_cursor_same_row
+ local at_edge_behavior = config.at_edge
+
+ if type(opts) == 'table' then
+ if opts.same_row ~= nil then
+ same_row = opts.same_row
+ end
+ if opts.at_edge ~= nil then
+ at_edge_behavior = opts.at_edge
+ end
+ end
+
+ -- Handle floating windows: just forward to Herdr,
+ -- EXCEPT for embedded sidebars (snacks/neo-tree float/aerial float):
+ -- those should stay in Neovim so the user can keep interacting
+ -- with the picker.
+ if win.is_floating() and not win.is_embedded_floating_window() then
+ herdr.focus_pane(direction)
+ return
+ end
+
+ local dir_key = win.dir_keys[direction]
+ local offset = vim.fn.winline() + vim.api.nvim_win_get_position(0)[1]
+
+ -- Save current window to detect if wincmd changes it
+ local prev_win = vim.api.nvim_get_current_win()
+
+ -- Try moving within Neovim first
+ local will_wrap = false
+ local count = vim.v.count1
+ local target_winnr = vim.fn.winnr(count .. dir_key)
+ if count > 1 then
+ local prev_winnr = vim.fn.winnr((count - 1) .. dir_key)
+ will_wrap = target_winnr == prev_winnr
+ else
+ will_wrap = target_winnr == vim.fn.winnr()
+ end
+
+ -- Command-line window (q:, q/, q?): Neovim forbids all window commands
+ -- (E11). Never wincmd; at a Neovim screen edge, delegate to Herdr
+ -- (subprocess-safe, does not close the cmdwin); otherwise silent no-op.
+ -- Mirrors smart-splits.nvim PR #464.
+ if win.is_command_line_window() then
+ if will_wrap and herdr.is_in_session() then
+ local at_herdr_edge = herdr.current_pane_at_edge(direction)
+ if at_herdr_edge == false then
+ herdr.focus_pane(direction)
+ elseif at_herdr_edge == true and herdr.nav_at_edge() ~= 'stop' then
+ herdr.focus_pane(win.reverse_direction[direction])
+ end
+ end
+ return
+ end
+
+ -- Execute the wincmd
+ if will_wrap and count == 1 then
+ vim.cmd('wincmd ' .. dir_key)
+ else
+ vim.cmd(count .. 'wincmd ' .. dir_key)
+ end
+
+ if vim.api.nvim_get_current_win() ~= prev_win then
+ -- Moved within Neovim. Restore same-row if configured.
+ if (direction == 'left' or direction == 'right') and same_row then
+ local row = offset - vim.api.nvim_win_get_position(0)[1]
+ if row > 0 then
+ vim.cmd('normal! ' .. row .. 'H')
+ end
+ end
+ return
+ end
+
+ -- We're at a Neovim edge. Try to cross into Herdr.
+ if not herdr.is_in_session() then
+ if will_wrap and count == 1 then
+ local sidebar = is_sidebar()
+ if type(at_edge_behavior) == 'function' then
+ at_edge_behavior({
+ direction = direction,
+ split = function() split_edge(direction) end,
+ is_sidebar = sidebar,
+ wrap = function()
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end,
+ })
+ elseif at_edge_behavior == 'stop' then
+ return
+ elseif at_edge_behavior == 'split' then
+ if not sidebar then
+ split_edge(direction)
+ end
+ else -- 'wrap' (default)
+ -- Wrap is allowed even from filetype-based sidebars (dbui, neo-tree,
+ -- ...) so the user can leave them; only embedded floats stay gated.
+ if not win.is_embedded_floating_window() then
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ end
+ end
+ return
+ end
+
+ -- Check zoom state: unzoom first, then retry Neovim navigation.
+ -- Must happen BEFORE the at_herdr_edge check — when zoomed, the pane fills
+ -- the screen so herdr reports it as being at every edge, making
+ -- at_herdr_edge useless until we unzoom.
+ if herdr.unzoom_enabled() and herdr.current_pane_is_zoomed() then
+ herdr.unzoom()
+ -- Retry wincmd — other Neovim splits may now be visible
+ vim.cmd('wincmd ' .. dir_key)
+ if vim.api.nvim_get_current_win() ~= prev_win then
+ if (direction == 'left' or direction == 'right') and same_row then
+ local row = offset - vim.api.nvim_win_get_position(0)[1]
+ if row > 0 then
+ vim.cmd('normal! ' .. row .. 'H')
+ end
+ end
+ return
+ end
+ -- Still at edge after unzoom; fall through to Herdr edge check.
+ end
+
+ -- Check if we're at the Herdr edge too
+ local at_herdr_edge = herdr.current_pane_at_edge(direction)
+ if at_herdr_edge == nil then
+ if will_wrap and count == 1 and not win.is_embedded_floating_window() then
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ return
+ end
+
+ if not at_herdr_edge then
+ -- There's a Herdr pane in this direction. Cross the boundary.
+ local moved = herdr.focus_pane(direction)
+ if not moved and will_wrap and count == 1 and not win.is_embedded_floating_window() then
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ return
+ end
+
+ -- At both Neovim AND Herdr edges (no herdr pane to cross into).
+ -- Apply at_edge behavior. (Any needed unzoom already happened above.)
+ if type(at_edge_behavior) == 'function' then
+ at_edge_behavior({
+ direction = direction,
+ split = function() split_edge(direction) end,
+ is_sidebar = is_sidebar(),
+ wrap = function()
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end,
+ })
+ elseif at_edge_behavior == 'stop' then
+ return
+ elseif at_edge_behavior == 'split' then
+ if not is_sidebar() then
+ split_edge(direction)
+ end
+ else -- 'wrap' (default)
+ -- Wrap is allowed from filetype-based sidebars (dbui, neo-tree, ...) so
+ -- you can leave them at an edge; only embedded floating overlays stay gated.
+ if will_wrap and count == 1 and not win.is_embedded_floating_window() then
+ -- Wrap to the opposite side. If a Herdr pane exists there AND nav_at_edge
+ -- allows wrap-across-boundary (the default), cross into it; otherwise
+ -- wrap within Neovim. nav_at_edge=stop keeps the wrap inside Neovim.
+ if herdr.nav_at_edge() ~= 'stop'
+ and herdr.current_pane_at_edge(win.reverse_direction[direction]) == false
+ then
+ herdr.focus_pane(win.reverse_direction[direction])
+ else
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ end
+ end
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/resize.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/resize.lua
new file mode 100644
index 0000000..ff94999
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/resize.lua
@@ -0,0 +1,108 @@
+---Resize logic: seamless resize between Neovim splits and Herdr panes.
+---
+---Mental model (same as smart-splits.nvim):
+--- The direction (up/down/left/right) is which way to move the split border.
+--- The +/- operator depends on whether current window is above/below or
+--- left/right of the neighbor being resized against.
+---
+---When at a Neovim edge with no neighbor in that direction, and the window
+---fills the terminal, the resize is forwarded to Herdr.
+---@class HerdrSplitsResize
+local M = {}
+
+local config = require('herdr-splits.config')
+local herdr = require('herdr-splits.herdr')
+local win = require('herdr-splits.win')
+
+M.is_resizing = false
+
+---Compute the +/- operator for a vertical resize.
+---At start/middle: resize_up shrinks (-), resize_down grows (+).
+---At last (bottom): inverted — resize_up grows (+), resize_down shrinks (-).
+---@param direction '"up"'|'"down"'
+---@return '"+""'|'"-""'
+local function compute_vertical(direction)
+ local pos = win.win_position(direction)
+ if pos == 'start' or pos == 'middle' then
+ return direction == 'down' and '+' or '-'
+ end
+ return direction == 'down' and '-' or '+'
+end
+
+---Compute the +/- operator for a horizontal resize.
+---At start/middle: resize_left shrinks (-), resize_right grows (+).
+---At last (right): inverted — resize_left grows (+), resize_right shrinks (-).
+---@param direction '"left"'|'"right"'
+---@return '"+""'|'"-""'
+local function compute_horizontal(direction)
+ local pos = win.win_position(direction)
+ if pos == 'start' or pos == 'middle' then
+ return direction == 'right' and '+' or '-'
+ end
+ return direction == 'right' and '-' or '+'
+end
+
+---Resize in a direction.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@param amount number|nil Override amount in Neovim cells. When nil, uses
+--- vim.v.count1 * neovim_amount for native or count * default_amount for Herdr.
+function M.resize(direction, amount)
+ local count = vim.v.count1
+ local has_explicit = amount ~= nil
+
+ -- Embedded floating sidebars (snacks float, neo-tree float, aerial float):
+ -- refuse to resize; the picker owns its own dimensions.
+ if win.is_embedded_floating_window() then
+ return
+ end
+
+ -- Floating windows: forward to Herdr
+ if win.is_floating() then
+ local ratio = has_explicit and amount or (count * config.default_amount)
+ herdr.resize_pane(direction, ratio)
+ return
+ end
+
+ -- Delegate to Herdr ONLY when at both Neovim edges in this dimension
+ -- AND the window fills the terminal (full_width or full_height).
+ local delegate = false
+ -- Never delegate from inside a sidebar; the ignore list applies to resize
+ -- too. The command-line window is exempted: it is `nofile` (so it trips this
+ -- rule) yet a full-width cmdwin should still resize the Herdr pane sideways.
+ local in_sidebar = win.is_ignored_or_preview()
+ if direction == 'left' or direction == 'right' then
+ delegate = win.is_full_width() and win.at_left_edge() and win.at_right_edge() and herdr.is_in_session()
+ else
+ delegate = win.is_full_height() and win.at_top_edge() and win.at_bottom_edge() and herdr.is_in_session()
+ end
+ if delegate and in_sidebar and not win.is_command_line_window() then
+ delegate = false
+ end
+
+ if delegate and herdr.current_pane_is_zoomed() then
+ delegate = false
+ end
+
+ if delegate then
+ local ratio = (has_explicit and amount < 1) and amount or (count * config.default_amount)
+ herdr.resize_pane(direction, ratio)
+ return
+ end
+
+ -- Native Neovim resize
+ local cells = has_explicit and math.floor(amount) or (count * config.neovim_amount)
+ if cells <= 0 then
+ cells = 1
+ end
+
+ local is_horiz = direction == 'left' or direction == 'right'
+ local op = is_horiz and compute_horizontal(direction) or compute_vertical(direction)
+
+ if is_horiz then
+ pcall(vim.cmd, 'vertical resize ' .. op .. cells)
+ else
+ pcall(vim.cmd, 'resize ' .. op .. cells)
+ end
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/sync.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/sync.lua
new file mode 100644
index 0000000..ddef394
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/sync.lua
@@ -0,0 +1,107 @@
+---Auto-sync the Herdr-managed checkout to match the lazy.nvim checkout.
+---
+---Herdr has no `plugin update` command in v1; the only sanctioned refresh is
+---reinstall from GitHub. When lazy.nvim pulls a new commit, the bash scripts
+---under the Herdr-managed checkout stay frozen at the old commit unless the
+---user manually reinstalls. This module closes that gap: it reinstalls the
+---Herdr-managed checkout pinned to the exact commit lazy fetched, so the bash
+---scripts match the lua side byte-for-byte.
+---
+---Entirely a no-op unless `auto_sync_herdr = true` is set in setup(). Also a
+---no-op when: the herdr binary is unavailable, the plugin is installed as a
+---local link (dev mode — reinstall would be refused anyway), the plugin is not
+---installed, or the commits already match.
+local M = {}
+
+local config = require('herdr-splits.config')
+
+---Plugin id and GitHub source as used by `herdr plugin install`.
+local PLUGIN_ID = 'herdr-splits'
+local PLUGIN_SOURCE = 'lmilojevicc/herdr-splits.nvim'
+
+---Run the sync. Safe to call at any time; never throws.
+function M.sync()
+ if config.auto_sync_herdr ~= true then
+ return
+ end
+
+ local ok, err = pcall(function()
+ local bin = config.herdr_bin or vim.env.HERDR_BIN_PATH or 'herdr'
+ if vim.fn.executable(bin) ~= 1 then
+ return
+ end
+
+ -- Resolve this plugin's own checkout root (the lazy clone).
+ local source = debug.getinfo(1, 'S').source -- "@<abs>/lua/herdr-splits/sync.lua"
+ local this_file = source:sub(2) -- strip leading '@'
+ local plugin_root = vim.fn.fnamemodify(this_file, ':p:h:h:h') -- up 3: file<-herdr-splits<-lua<-root
+ if vim.fn.isdirectory(plugin_root .. '/.git') == 0
+ and vim.fn.filereadable(plugin_root .. '/herdr-plugin.toml') == 0 then
+ return -- not a real checkout
+ end
+
+ -- Get the lazy checkout HEAD.
+ local obj = vim.system({ 'git', '-C', plugin_root, 'rev-parse', 'HEAD' }, { text = true }):wait()
+ if obj.code ~= 0 then
+ return
+ end
+ local lazy_sha = vim.trim(obj.stdout or '')
+ if lazy_sha == '' then
+ return
+ end
+
+ -- Get the managed install info.
+ local obj2 = vim.system({ bin, 'plugin', 'list', '--plugin', PLUGIN_ID, '--json' }, { text = true }):wait()
+ if obj2.code ~= 0 then
+ return
+ end
+
+ local decoded, data = pcall(vim.json.decode, obj2.stdout or '')
+ if not decoded or type(data) ~= 'table' then
+ return
+ end
+
+ -- Output may be a single object or an array; find our entry.
+ local entry = data
+ if data[1] ~= nil then
+ for _, e in ipairs(data) do
+ if e.plugin_id == PLUGIN_ID then
+ entry = e
+ break
+ end
+ end
+ end
+
+ local src = entry and entry.source
+ if not src then
+ return
+ end
+ -- Dev mode (plugin link) or unknown kind: nothing to do.
+ if src.kind ~= 'github' then
+ return
+ end
+
+ -- Already in sync?
+ local managed_sha = vim.trim(src.resolved_commit or '')
+ if managed_sha ~= '' and managed_sha:lower() == lazy_sha:lower() then
+ return
+ end
+
+ -- Reinstall pinned to the lazy commit.
+ local obj3 = vim.system(
+ { bin, 'plugin', 'install', PLUGIN_SOURCE, '--ref', lazy_sha, '--yes' },
+ { text = true }
+ ):wait()
+ if obj3.code == 0 then
+ vim.notify('herdr-splits: synced Herdr-side scripts to ' .. lazy_sha:sub(1, 7), vim.log.levels.INFO)
+ else
+ vim.notify('herdr-splits: Herdr-side sync skipped (install failed; local link or offline)', vim.log.levels.INFO)
+ end
+ end)
+
+ if not ok then
+ vim.notify('herdr-splits: sync error: ' .. tostring(err), vim.log.levels.DEBUG)
+ end
+end
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/win.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/win.lua
new file mode 100644
index 0000000..47f6821
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/win.lua
@@ -0,0 +1,154 @@
+---Window utility functions for Neovim split detection.
+---@class HerdrSplitsWin
+local M = {}
+
+local config = require('herdr-splits.config')
+
+---Check if the current window spans the full terminal width.
+---@param winid number|nil window ID, defaults to current
+---@return boolean
+function M.is_full_width(winid)
+ return vim.api.nvim_win_get_width(winid or 0) == vim.o.columns
+end
+
+---Check if the current window spans the full terminal height.
+---Accounts for cmdheight, statusline, and tabline.
+---@param winid number|nil window ID, defaults to current
+---@return boolean
+function M.is_full_height(winid)
+ local height = vim.o.lines - vim.o.cmdheight
+ local tabpages = #vim.api.nvim_list_tabpages()
+ local wins = #vim.api.nvim_tabpage_list_wins(0)
+
+ if (vim.o.laststatus == 1 and wins > 1) or vim.o.laststatus > 1 then
+ height = height - 1
+ end
+ if (vim.o.showtabline == 1 and tabpages > 1) or vim.o.showtabline == 2 then
+ height = height - 1
+ end
+
+ return vim.api.nvim_win_get_height(winid or 0) == height
+end
+
+---@return boolean
+function M.at_left_edge()
+ return vim.fn.winnr() == vim.fn.winnr('h')
+end
+
+---@return boolean
+function M.at_right_edge()
+ return vim.fn.winnr() == vim.fn.winnr('l')
+end
+
+---@return boolean
+function M.at_top_edge()
+ return vim.fn.winnr() == vim.fn.winnr('k')
+end
+
+---@return boolean
+function M.at_bottom_edge()
+ return vim.fn.winnr() == vim.fn.winnr('j')
+end
+
+---Determine where the current window sits in the Neovim split layout
+---for a given direction (horizontal or vertical).
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@return '"start"'|'"middle"'|'"last"'
+function M.win_position(direction)
+ if direction == 'left' or direction == 'right' then
+ if M.at_left_edge() then
+ return 'start'
+ end
+ if M.at_right_edge() then
+ return 'last'
+ end
+ return 'middle'
+ end
+
+ if M.at_top_edge() then
+ return 'start'
+ end
+ if M.at_bottom_edge() then
+ return 'last'
+ end
+ return 'middle'
+end
+
+---Check if a window should be ignored during resize operations.
+---@param winid number|nil window ID, defaults to current
+---@return boolean
+function M.is_ignored_win(winid)
+ local bufnr = vim.api.nvim_win_get_buf(winid or 0)
+ return vim.tbl_contains(config.ignored_buftypes, vim.api.nvim_get_option_value('buftype', { buf = bufnr }))
+ or vim.tbl_contains(config.ignored_filetypes, vim.api.nvim_get_option_value('filetype', { buf = bufnr }))
+end
+
+---Check if the current window is a floating window.
+---@param winid number|nil
+---@return boolean
+function M.is_floating(winid)
+ return vim.api.nvim_win_get_config(winid or 0).relative ~= ''
+end
+
+---Check if a window is an "embedded" floating window — one that is technically
+---floating (relative ~= '') but visually behaves like a sidebar (e.g. snacks
+---explorer). Neovim's default floating zindex is 50; anything explicitly set
+---below that signals the window is meant to coexist with normal splits.
+---@param winid number|nil window ID, defaults to current
+---@return boolean
+function M.is_embedded_floating_window(winid)
+ if not M.is_floating(winid) then
+ return false
+ end
+ local cfg = vim.api.nvim_win_get_config(winid or 0)
+ local threshold = config.floating_zindex_max or 50
+ return cfg.zindex ~= nil and cfg.zindex < threshold
+end
+
+---Same as M.is_ignored_win but also checks previewwindow when opt-in.
+---@param winid number|nil window ID, defaults to current
+---@return boolean
+function M.is_ignored_or_preview(winid)
+ if M.is_ignored_win(winid) then
+ return true
+ end
+ if config.ignore_previewwindows then
+ local ok, pw = pcall(vim.api.nvim_win_get_option, winid or 0, 'previewwindow')
+ if ok and pw then
+ return true
+ end
+ end
+ return false
+end
+
+---Returns true while Neovim's command-line window (q:, q/, q?) is open.
+---Inside it all window commands raise E11, so callers must short-circuit.
+---@return boolean
+function M.is_command_line_window()
+ return vim.fn.getcmdwintype() ~= ''
+end
+
+---Direction key shorthand for wincmd.
+M.dir_keys = {
+ left = 'h',
+ right = 'l',
+ up = 'k',
+ down = 'j',
+}
+
+M.dir_keys_reverse = {
+ left = 'l',
+ right = 'h',
+ up = 'j',
+ down = 'k',
+}
+
+---Reverse of a direction name: left<->right, up<->down.
+M.reverse_direction = {
+ left = 'right',
+ right = 'left',
+ up = 'down',
+ down = 'up',
+}
+
+return M
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/plugin/herdr-splits.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/plugin/herdr-splits.lua
new file mode 100644
index 0000000..4d32363
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/plugin/herdr-splits.lua
@@ -0,0 +1,11 @@
+-- herdr-splits.nvim plugin bootstrap.
+-- This file is loaded by Neovim when the plugin is found on the runtimepath.
+-- It defers all work to `require('herdr-splits')`.
+
+if vim.g.loaded_herdr_splits then
+ return
+end
+vim.g.loaded_herdr_splits = true
+
+-- Defer loading until user calls setup() or invokes a function.
+-- The main module is available as require('herdr-splits').
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-nav.sh b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-nav.sh
new file mode 100644
index 0000000..9cdbbb7
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-nav.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+# Herdr navigation helper — used by herdr keybinds for seamless two-way nav.
+#
+# When a navigation key is pressed in Herdr:
+# 1. Check if the focused pane is running Neovim in the foreground.
+# - If yes: forward the key chord into that pane. The Neovim plugin owns
+# in-split movement, edge crossing, and unzoom — do NOT unzoom here, or
+# moving between Neovim splits would incorrectly unzoom the pane.
+# 2. If no (a plain Herdr pane): unzoom if needed, then move Herdr pane focus.
+# At a layout edge, focus wraps around to the opposite side (smart-splits
+# style), so navigating past the last pane lands on the first — unless
+# `nav_at_edge=stop`, in which case it halts at the edge.
+#
+# Auto-unzoom is configurable via `unzoom_on_nav=false` in
+# `~/.config/herdr/plugins/config/herdr-splits/herdr-splits.conf`
+# (same file the Neovim plugin reads). Default: enabled.
+#
+# Edge behaviour for plain Herdr panes is configurable via `nav_at_edge` in
+# the same conf file: `wrap` (default; wraps to the opposite side at an edge)
+# or `stop` (navigation halts at the edge instead of wrapping).
+#
+# Usage: herdr-nav.sh <left|down|up|right>
+
+set -euo pipefail
+
+dir="${1:?usage: herdr-nav.sh <left|down|up|right>}"
+herdr="${HERDR_BIN_PATH:-herdr}"
+
+case "$dir" in
+ left) key="ctrl+h"; config_key="nav_key_left"; opp="right" ;;
+ down) key="ctrl+j"; config_key="nav_key_down"; opp="up" ;;
+ up) key="ctrl+k"; config_key="nav_key_up"; opp="down" ;;
+ right) key="ctrl+l"; config_key="nav_key_right"; opp="left" ;;
+ *) echo "herdr-nav.sh: unknown direction: $dir" >&2; exit 2 ;;
+esac
+
+# Resolve `unzoom_on_nav` and `nav_at_edge` from the shared config file.
+# Defaults: unzoom enabled, nav_at_edge=wrap (backward compatible).
+unzoom=1
+nav_at_edge=wrap
+config_path="${HERDR_SPLITS_CONFIG:-${HERDR_PLUGIN_CONFIG_DIR:-$HOME/.config/herdr/plugins/config/herdr-splits}/herdr-splits.conf}"
+if [ -r "$config_path" ]; then
+ configured_key=$(sed -n -E "s/^[[:space:]]*${config_key}[[:space:]]*=[[:space:]]*([^[:space:]#]+).*$/\\1/p" "$config_path" | tail -n 1)
+ if [ -n "$configured_key" ]; then
+ key="$configured_key"
+ fi
+ if grep -Eq '^[[:space:]]*unzoom_on_nav[[:space:]]*=[[:space:]]*false' "$config_path"; then
+ unzoom=0
+ fi
+ if grep -Eq '^[[:space:]]*nav_at_edge[[:space:]]*=[[:space:]]*stop' "$config_path"; then
+ nav_at_edge=stop
+ fi
+fi
+
+# Get focused pane ID from server (best-effort; empty is fine — we fall
+# through to the non-vim path below, which uses --current directly).
+pane_id=$("$herdr" pane current --current 2>/dev/null | grep -o '"pane_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/' || true)
+
+# Check if focused pane is running vim/nvim as foreground process.
+is_vim=0
+if [ -n "$pane_id" ] && pane_info=$("$herdr" pane process-info --current 2>/dev/null); then
+ if echo "$pane_info" | grep -qiE '"name"[[:space:]]*:[[:space:]]*"(g?(view|l?n?vim?x?)(diff)?)"' 2>/dev/null; then
+ is_vim=1
+ fi
+fi
+
+# Vim pane: forward the chord; the Neovim plugin decides movement + unzoom.
+if [ "$is_vim" -eq 1 ]; then
+ exec "$herdr" pane send-keys "$pane_id" "$key"
+fi
+
+# --- Non-vim Herdr pane: unzoom if needed, then focus (wrapping at edges) ---
+
+# `pane edges` reports both edge flags and the zoomed state in one call.
+edges_out=$("$herdr" pane edges --current 2>/dev/null || true)
+
+# A zoomed pane fills the tab and reports itself at every edge, so the edge
+# flags are useless for the wrap decision. Unzoom (if enabled) so we can
+# trust them. If unzoom is disabled we leave zoom untouched and must NOT use
+# the (all-true) edge flags — wrap is skipped below in that case.
+edges_trusted=1
+if printf '%s' "$edges_out" | grep -q '"zoomed"[[:space:]]*:[[:space:]]*true'; then
+ if [ "$unzoom" -eq 1 ]; then
+ "$herdr" pane zoom --off --current 2>/dev/null || true
+ # Layout changed; re-read edges so the wrap check is accurate.
+ edges_out=$("$herdr" pane edges --current 2>/dev/null || true)
+ else
+ edges_trusted=0
+ fi
+fi
+
+# Move to the neighbor in the requested direction. When already at the
+# requested edge (no neighbor there): wrap to the opposite side, or — when
+# nav_at_edge=stop — do nothing. Skip the edge check when the flags are
+# unreliable (still zoomed, unzoom disabled); in that case we can't tell
+# whether we're at an edge, so we just focus in the requested direction.
+if [ "$edges_trusted" -eq 1 ] && printf '%s' "$edges_out" | grep -q "\"$dir\"[[:space:]]*:[[:space:]]*true"; then
+ if [ "$nav_at_edge" = stop ]; then
+ exit 0
+ fi
+ exec "$herdr" pane focus --direction "$opp" --current
+else
+ exec "$herdr" pane focus --direction "$dir" --current
+fi
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-resize.sh b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-resize.sh
new file mode 100644
index 0000000..16d0b0d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/executable_herdr-resize.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+# Herdr resize helper — used by herdr keybinds for seamless two-way resize.
+#
+# When a resize key (alt+h/j/k/l) is pressed in Herdr:
+# 1. Check if the focused pane is running Neovim in the foreground
+# 2. If yes: forward the key chord into that pane (Neovim's plugin handles it)
+# 3. If no: resize Herdr pane directly
+#
+# Zoom state is left untouched (matches smart-splits.nvim's
+# disable_multiplexer_nav_when_zoomed default).
+#
+# Usage: herdr-resize.sh <left|down|up|right> [amount]
+
+set -euo pipefail
+
+dir="${1:?usage: herdr-resize.sh <left|down|up|right> [amount]}"
+amount="${2:-0.03}"
+herdr="${HERDR_BIN_PATH:-herdr}"
+
+case "$dir" in
+ left) key="alt+h"; config_key="resize_key_left" ;;
+ down) key="alt+j"; config_key="resize_key_down" ;;
+ up) key="alt+k"; config_key="resize_key_up" ;;
+ right) key="alt+l"; config_key="resize_key_right" ;;
+ *) echo "herdr-resize.sh: unknown direction: $dir" >&2; exit 2 ;;
+esac
+
+# Resolve the forward chord from the shared config file.
+# Unset values keep the alt+hjkl default.
+config_path="${HERDR_SPLITS_CONFIG:-${HERDR_PLUGIN_CONFIG_DIR:-$HOME/.config/herdr/plugins/config/herdr-splits}/herdr-splits.conf}"
+if [ -r "$config_path" ]; then
+ configured_key=$(sed -n -E "s/^[[:space:]]*${config_key}[[:space:]]*=[[:space:]]*([^[:space:]#]+).*$/\\1/p" "$config_path" | tail -n 1)
+ if [ -n "$configured_key" ]; then
+ key="$configured_key"
+ fi
+fi
+
+# Get focused pane ID from server
+pane_id=$("$herdr" pane current --current 2>/dev/null | grep -o '"pane_id"\s*:\s*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
+if [ -z "$pane_id" ]; then
+ exec "$herdr" pane resize --direction "$dir" --amount "$amount" --current
+fi
+
+# Check if focused pane is running vim/nvim as foreground process.
+# When it is, the key is forwarded into Neovim so the plugin can decide
+# whether to resize a Neovim split or hand off to Herdr.
+is_vim=0
+if pane_info=$("$herdr" pane process-info --current 2>/dev/null); then
+ if echo "$pane_info" | grep -qiE '"name"\s*:\s*"(g?(view|l?n?vim?x?)(diff)?)"' 2>/dev/null; then
+ is_vim=1
+ fi
+fi
+
+if [ "$is_vim" -eq 1 ]; then
+ exec "$herdr" pane send-keys "$pane_id" "$key"
+else
+ exec "$herdr" pane resize --direction "$dir" --amount "$amount" --current
+fi
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/minimal_init.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/minimal_init.lua
new file mode 100644
index 0000000..3fe8254
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/scripts/minimal_init.lua
@@ -0,0 +1,14 @@
+local source = debug.getinfo(1, 'S').source
+assert(source:sub(1, 1) == '@', 'could not locate minimal_init.lua')
+
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+vim.opt.runtimepath:prepend(root)
+vim.opt.runtimepath:prepend(root .. '/deps/mini.test')
+
+require('mini.test').setup({
+ collect = {
+ find_files = function()
+ return vim.fn.glob(root .. '/tests/test_*.lua', true, true)
+ end,
+ },
+})
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_conf.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_conf.lua
new file mode 100644
index 0000000..52f002f
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_conf.lua
@@ -0,0 +1,196 @@
+local T
+local expect = MiniTest.expect
+local child = MiniTest.new_child_neovim()
+
+local source = debug.getinfo(1, 'S').source
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+
+local function write_config(lines)
+ child.lua_func(function(config_lines)
+ local path = vim.env.HERDR_SPLITS_CONFIG
+ vim.fn.mkdir(vim.fn.fnamemodify(path, ':h'), 'p')
+ vim.fn.writefile(config_lines, path)
+ end, lines)
+end
+
+T = MiniTest.new_set({
+ hooks = {
+ pre_case = function()
+ child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
+ child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
+ child.lua([[
+ local test_dir = vim.fn.tempname()
+ vim.fn.mkdir(test_dir, 'p')
+ vim.g.herdr_splits_test_dir = test_dir
+ vim.env.HOME = test_dir .. '/home'
+ vim.env.XDG_CONFIG_HOME = test_dir .. '/xdg'
+ vim.env.HERDR_SPLITS_CONFIG = test_dir .. '/nested/herdr-splits.conf'
+ vim.env.HERDR_BIN_PATH = test_dir .. '/missing-herdr'
+ vim.env.HERDR_PLUGIN_CONFIG_DIR = nil
+ ]])
+ end,
+ post_case = function()
+ local test_dir = child.g.herdr_splits_test_dir
+ child.stop()
+ vim.fn.delete(test_dir, 'rf')
+ end,
+ },
+})
+
+T['to_herdr translates key notation'] = function()
+ local translate = function(value)
+ return child.lua_func(function(key)
+ return require('herdr-splits.conf').to_herdr(key)
+ end, value)
+ end
+
+ expect.equality(translate('<C-M-Left>'), 'ctrl+alt+left')
+ expect.equality(translate('<A-Return>'), 'alt+enter')
+ expect.equality(translate('<S-Space>'), 'shift+space')
+ expect.equality(translate('H'), 'h')
+ expect.equality(translate('<Escape>'), 'esc')
+ expect.equality(translate(''), vim.NIL)
+ expect.equality(translate(false), vim.NIL)
+ expect.equality(translate('<C>'), vim.NIL)
+end
+
+T['read_managed handles a missing file'] = function()
+ local result = child.lua_func(function()
+ local values, adopted = require('herdr-splits.conf').read_managed()
+ return { adopted = adopted, count = vim.tbl_count(values) }
+ end)
+
+ expect.equality(result, { adopted = false, count = 0 })
+end
+
+T['read_managed adopts managed legacy values with last value winning'] = function()
+ write_config({
+ '# legacy config',
+ ' nav_key_left = ctrl+old',
+ 'nav_key_left=ctrl+new # last value wins',
+ 'resize_key_up = alt+up',
+ 'unzoom_on_nav=false',
+ 'nav_at_edge=stop',
+ 'unknown_key=ignored',
+ })
+
+ local result = child.lua_func(function()
+ local values, adopted = require('herdr-splits.conf').read_managed()
+ return {
+ adopted = adopted,
+ nav_left = values.nav_keys.left,
+ resize_up = values.resize_keys.up,
+ unzoom_on_nav = values.unzoom_on_nav,
+ nav_at_edge = values.nav_at_edge,
+ unknown = values.unknown_key,
+ }
+ end)
+
+ expect.equality(result, {
+ adopted = true,
+ nav_left = 'ctrl+new',
+ resize_up = 'alt+up',
+ unzoom_on_nav = false,
+ nav_at_edge = 'stop',
+ })
+end
+
+T['write creates generated content without leftover temporary files'] = function()
+ local result = child.lua_func(function()
+ local conf = require('herdr-splits.conf')
+ local ok, err = conf.write({
+ nav_keys = { left = 'ctrl+h', down = 'ctrl+j', up = 'ctrl+k', right = 'ctrl+l' },
+ resize_keys = { left = 'alt+h', down = 'alt+j', up = 'alt+k', right = 'alt+l' },
+ unzoom_on_nav = false,
+ nav_at_edge = 'stop',
+ })
+ local values, adopted = conf.read_managed()
+ return {
+ ok = ok,
+ err = err,
+ adopted = adopted,
+ adopted_count = vim.tbl_count(values),
+ }
+ end)
+
+ expect.equality(result, { ok = true, adopted = false, adopted_count = 0 })
+
+ local content = child.lua_func(function()
+ local file = assert(io.open(vim.env.HERDR_SPLITS_CONFIG, 'r'))
+ local bytes = file:read('*a')
+ file:close()
+ return bytes
+ end)
+ expect.equality(content, table.concat({
+ '# herdr-splits-generated-v1',
+ '# herdr-splits plugin config — GENERATED by herdr-splits.nvim setup().',
+ '# Regenerated from defaults + setup() opts on every setup(); do not edit by hand.',
+ '# A headerless/legacy conf is adopted once on migration (see the startup',
+ '# notice), then overwritten thereafter — remove an opt in setup() to revert.',
+ 'nav_key_left=ctrl+h',
+ 'nav_key_down=ctrl+j',
+ 'nav_key_up=ctrl+k',
+ 'nav_key_right=ctrl+l',
+ 'resize_key_left=alt+h',
+ 'resize_key_down=alt+j',
+ 'resize_key_up=alt+k',
+ 'resize_key_right=alt+l',
+ 'unzoom_on_nav=false',
+ 'nav_at_edge=stop',
+ '',
+ }, '\n'))
+ expect.equality(child.lua_get([[vim.fn.glob(vim.env.HERDR_SPLITS_CONFIG .. '.tmp.*', false, true)]]), {})
+end
+
+T['config setup applies precedence and resets omitted managed options'] = function()
+ write_config({
+ 'nav_key_left=ctrl+legacy',
+ 'resize_key_left=alt+legacy',
+ 'unzoom_on_nav=false',
+ 'nav_at_edge=stop',
+ })
+
+ local result = child.lua_func(function()
+ vim.notify = function() end
+ local config = require('herdr-splits.config')
+
+ config.setup({
+ nav_keys = { left = '<C-x>' },
+ unzoom_on_nav = true,
+ })
+ local first = {
+ nav_left = config.nav_keys.left,
+ resize_left = config.resize_keys.left,
+ unzoom_on_nav = config.unzoom_on_nav,
+ nav_at_edge = config.nav_at_edge,
+ }
+
+ config.setup({ nav_keys = { right = '<C-r>' } })
+ local second = {
+ nav_left = config.nav_keys.left,
+ nav_right = config.nav_keys.right,
+ resize_left = config.resize_keys.left,
+ unzoom_on_nav = config.unzoom_on_nav,
+ nav_at_edge = config.nav_at_edge,
+ }
+ return { first = first, second = second }
+ end)
+
+ expect.equality(result, {
+ first = {
+ nav_left = 'ctrl+x',
+ resize_left = 'alt+legacy',
+ unzoom_on_nav = true,
+ nav_at_edge = 'stop',
+ },
+ second = {
+ nav_left = 'ctrl+h',
+ nav_right = 'ctrl+r',
+ resize_left = 'alt+h',
+ unzoom_on_nav = true,
+ nav_at_edge = 'wrap',
+ },
+ })
+end
+
+return T
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_herdr.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_herdr.lua
new file mode 100644
index 0000000..aecd3e0
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_herdr.lua
@@ -0,0 +1,267 @@
+local T
+local expect = MiniTest.expect
+local child = MiniTest.new_child_neovim()
+
+local source = debug.getinfo(1, 'S').source
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+
+T = MiniTest.new_set({
+ hooks = {
+ pre_case = function()
+ child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
+ child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
+ end,
+ post_case = function()
+ child.stop()
+ end,
+ },
+})
+
+T['resolves the binary and requires a complete session environment'] = function()
+ local result = child.lua_func(function()
+ local config = require('herdr-splits.config')
+ local herdr = require('herdr-splits.herdr')
+
+ vim.env.HERDR_BIN_PATH = '/env/herdr'
+ config.herdr_bin = nil
+ local from_env = herdr.herdr_bin()
+ config.herdr_bin = '/config/herdr'
+ local from_config = herdr.herdr_bin()
+ config.herdr_bin = nil
+ vim.env.HERDR_BIN_PATH = nil
+ local fallback = herdr.herdr_bin()
+
+ vim.env.HERDR_ENV = nil
+ vim.env.HERDR_PANE_ID = nil
+ local absent = { herdr.is_in_session(), herdr.current_pane_id() or '<nil>' }
+ vim.env.HERDR_ENV = '1'
+ vim.env.HERDR_PANE_ID = ''
+ local empty = { herdr.is_in_session(), herdr.current_pane_id() or '<nil>' }
+ vim.env.HERDR_PANE_ID = 'pane-7'
+ local present = { herdr.is_in_session(), herdr.current_pane_id() }
+
+ return {
+ binaries = { from_env, from_config, fallback },
+ absent = absent,
+ empty = empty,
+ present = present,
+ }
+ end)
+
+ expect.equality(result, {
+ binaries = { '/env/herdr', '/config/herdr', 'herdr' },
+ absent = { false, '<nil>' },
+ empty = { false, '<nil>' },
+ present = { true, 'pane-7' },
+ })
+end
+
+T['parses edge responses and rejects invalid command output'] = function()
+ local result = child.lua_func(function()
+ vim.env.HERDR_ENV = '1'
+ vim.env.HERDR_PANE_ID = 'pane-7'
+ local config = require('herdr-splits.config')
+ config.herdr_bin = '/fake/herdr'
+
+ local queue = {
+ { stdout = vim.json.encode({ result = { edges = { left = true } } }), stderr = '', code = 0 },
+ { stdout = vim.json.encode({ result = { edges = { left = false } } }), stderr = '', code = 0 },
+ { stdout = '{bad', stderr = '', code = 0 },
+ { stdout = '', stderr = '', code = 0 },
+ { stdout = vim.json.encode({ result = {} }), stderr = '', code = 0 },
+ { stdout = '{}', stderr = 'failed', code = 2 },
+ { stdout = vim.json.encode({ result = { edges = {} } }), stderr = '', code = 0 },
+ { stdout = '{"result":{"edges":{"left":null}}}', stderr = '', code = 0 },
+ { stdout = vim.json.encode({ result = { edges = { left = 'yes' } } }), stderr = '', code = 0 },
+ }
+ local calls = {}
+ vim.system = function(argv, opts)
+ calls[#calls + 1] = { argv = vim.deepcopy(argv), opts = vim.deepcopy(opts) }
+ local item = table.remove(queue, 1)
+ return { wait = function() return item end }
+ end
+
+ package.loaded['herdr-splits.herdr'] = nil
+ local herdr = require('herdr-splits.herdr')
+ local values = {}
+ for i = 1, 9 do
+ local value = herdr.current_pane_at_edge('left')
+ values[i] = value == nil and '<nil>' or value
+ end
+ return { values = values, calls = calls }
+ end)
+
+ expect.equality(result.values, {
+ true, false, '<nil>', '<nil>', '<nil>', '<nil>', '<nil>', '<nil>', '<nil>',
+ })
+ expect.equality(#result.calls, 9)
+ for _, call in ipairs(result.calls) do
+ expect.equality(call, {
+ argv = { '/fake/herdr', 'pane', 'edges', '--current' },
+ opts = { text = true },
+ })
+ end
+end
+
+T['parses zoom responses and rejects invalid command output'] = function()
+ local result = child.lua_func(function()
+ vim.env.HERDR_ENV = '1'
+ vim.env.HERDR_PANE_ID = 'pane-7'
+ local queue = {
+ { stdout = vim.json.encode({ result = { layout = { zoomed = true } } }), stderr = '', code = 0 },
+ { stdout = vim.json.encode({ result = { layout = { zoomed = false } } }), stderr = '', code = 0 },
+ { stdout = '{bad', stderr = '', code = 0 },
+ { stdout = vim.json.encode({ result = {} }), stderr = '', code = 0 },
+ { stdout = '{}', stderr = '', code = 1 },
+ { stdout = vim.json.encode({ result = { layout = {} } }), stderr = '', code = 0 },
+ { stdout = '{"result":{"layout":{"zoomed":null}}}', stderr = '', code = 0 },
+ { stdout = vim.json.encode({ result = { layout = { zoomed = 1 } } }), stderr = '', code = 0 },
+ }
+ local calls = {}
+ vim.system = function(argv, opts)
+ calls[#calls + 1] = { argv = vim.deepcopy(argv), opts = vim.deepcopy(opts) }
+ local item = table.remove(queue, 1)
+ return { wait = function() return item end }
+ end
+
+ package.loaded['herdr-splits.herdr'] = nil
+ local herdr = require('herdr-splits.herdr')
+ local values = {}
+ for i = 1, 8 do
+ local value = herdr.current_pane_is_zoomed()
+ values[i] = value == nil and '<nil>' or value
+ end
+ return { values = values, calls = calls }
+ end)
+
+ expect.equality(result.values, {
+ true, false, '<nil>', '<nil>', '<nil>', '<nil>', '<nil>', '<nil>',
+ })
+ expect.equality(#result.calls, 8)
+ for _, call in ipairs(result.calls) do
+ expect.equality(call.argv, { 'herdr', 'pane', 'layout', '--current' })
+ expect.equality(call.opts, { text = true })
+ end
+end
+
+T['rejects JSON null for pane queries'] = function()
+ local result = child.lua_func(function()
+ vim.env.HERDR_ENV = '1'
+ vim.env.HERDR_PANE_ID = 'pane-7'
+ local queue = {
+ 'null',
+ '{"result":null}',
+ '{"result":{"edges":null}}',
+ 'null',
+ '{"result":null}',
+ '{"result":{"layout":null}}',
+ }
+ vim.system = function()
+ local stdout = table.remove(queue, 1)
+ return {
+ wait = function()
+ return { stdout = stdout, stderr = '', code = 0 }
+ end,
+ }
+ end
+
+ package.loaded['herdr-splits.herdr'] = nil
+ local herdr = require('herdr-splits.herdr')
+ local values = {}
+ for _ = 1, 3 do
+ local ok, value = pcall(herdr.current_pane_at_edge, 'left')
+ values[#values + 1] = { ok, value == nil and '<nil>' or value }
+ end
+ for _ = 1, 3 do
+ local ok, value = pcall(herdr.current_pane_is_zoomed)
+ values[#values + 1] = { ok, value == nil and '<nil>' or value }
+ end
+ return values
+ end)
+
+ expect.equality(result, {
+ { true, '<nil>' },
+ { true, '<nil>' },
+ { true, '<nil>' },
+ { true, '<nil>' },
+ { true, '<nil>' },
+ { true, '<nil>' },
+ })
+end
+
+T['focus and unzoom use exact commands and report exit status'] = function()
+ local result = child.lua_func(function()
+ vim.env.HERDR_ENV = '1'
+ vim.env.HERDR_PANE_ID = 'pane-7'
+ local queue = {
+ { stdout = '', stderr = '', code = 0 },
+ { stdout = '', stderr = 'no neighbor', code = 1 },
+ { stdout = '', stderr = '', code = 0 },
+ { stdout = '', stderr = 'failed', code = 1 },
+ }
+ local calls = {}
+ vim.system = function(argv, opts)
+ calls[#calls + 1] = { argv = vim.deepcopy(argv), opts = vim.deepcopy(opts) }
+ local item = table.remove(queue, 1)
+ return { wait = function() return item end }
+ end
+
+ package.loaded['herdr-splits.herdr'] = nil
+ local herdr = require('herdr-splits.herdr')
+ return {
+ values = {
+ herdr.focus_pane('right'),
+ herdr.focus_pane('left'),
+ herdr.unzoom(),
+ herdr.unzoom(),
+ },
+ calls = calls,
+ }
+ end)
+
+ expect.equality(result.values, { true, false, true, false })
+ expect.equality(result.calls, {
+ { argv = { 'herdr', 'pane', 'focus', '--direction', 'right', '--current' }, opts = { text = true } },
+ { argv = { 'herdr', 'pane', 'focus', '--direction', 'left', '--current' }, opts = { text = true } },
+ { argv = { 'herdr', 'pane', 'zoom', '--off', '--current' }, opts = { text = true } },
+ { argv = { 'herdr', 'pane', 'zoom', '--off', '--current' }, opts = { text = true } },
+ })
+end
+
+T['resize formats ratios and reports exit status'] = function()
+ local result = child.lua_func(function()
+ vim.env.HERDR_ENV = '1'
+ vim.env.HERDR_PANE_ID = 'pane-7'
+ local queue = {
+ { stdout = '', stderr = '', code = 0 },
+ { stdout = '', stderr = 'failed', code = 1 },
+ }
+ local calls = {}
+ vim.system = function(argv, opts)
+ calls[#calls + 1] = { argv = vim.deepcopy(argv), opts = vim.deepcopy(opts) }
+ local item = table.remove(queue, 1)
+ return { wait = function() return item end }
+ end
+
+ package.loaded['herdr-splits.herdr'] = nil
+ local herdr = require('herdr-splits.herdr')
+ return {
+ values = { herdr.resize_pane('down', 0.03), herdr.resize_pane('left', 1 / 3) },
+ calls = calls,
+ }
+ end)
+
+ expect.equality(result.values, { true, false })
+ expect.equality(result.calls, {
+ {
+ argv = { 'herdr', 'pane', 'resize', '--direction', 'down', '--amount', '0.0300', '--current' },
+ opts = { text = true },
+ },
+ {
+ argv = { 'herdr', 'pane', 'resize', '--direction', 'left', '--amount', '0.3333', '--current' },
+ opts = { text = true },
+ },
+ })
+end
+
+return T
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
new file mode 100644
index 0000000..f88a80d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
@@ -0,0 +1,520 @@
+local T
+local expect = MiniTest.expect
+local child = MiniTest.new_child_neovim()
+
+local source = debug.getinfo(1, 'S').source
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+
+T = MiniTest.new_set({
+ hooks = {
+ pre_case = function()
+ child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
+ child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
+ end,
+ post_case = function()
+ child.stop()
+ end,
+ },
+})
+
+T['moves through native horizontal and vertical splits without delegation'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ package.loaded['herdr-splits.herdr'] = {
+ focus_pane = function(direction) calls[#calls + 1] = direction end,
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local horizontal = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(horizontal, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(horizontal[1])
+ nav.move_cursor('right')
+ local moved_right = vim.api.nvim_get_current_win() == horizontal[2]
+
+ vim.cmd('only')
+ vim.o.splitbelow = true
+ vim.cmd('split')
+ local vertical = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(vertical, function(a, b)
+ return vim.api.nvim_win_get_position(a)[1] < vim.api.nvim_win_get_position(b)[1]
+ end)
+ vim.api.nvim_set_current_win(vertical[1])
+ nav.move_cursor('down')
+ local moved_down = vim.api.nvim_get_current_win() == vertical[2]
+
+ return { moved_right = moved_right, moved_down = moved_down, calls = calls }
+ end)
+
+ expect.equality(result, { moved_right = true, moved_down = true, calls = {} })
+end
+
+T['applies typed navigation counts without wrapping or delegation'] = function()
+ child.lua_func(function()
+ _G.nav_delegations = {}
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ focus_pane = function(direction)
+ _G.nav_delegations[#_G.nav_delegations + 1] = direction
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ vim.o.splitright = true
+ for _ = 1, 3 do
+ vim.cmd('vsplit')
+ end
+ _G.nav_count_wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(_G.nav_count_wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(_G.nav_count_wins[1])
+ vim.keymap.set('n', 'x', function()
+ nav.move_cursor('right', { at_edge = 'stop' })
+ end)
+ end)
+
+ child.type_keys('2x')
+ local after_two = child.lua_get('vim.api.nvim_get_current_win() == _G.nav_count_wins[3]')
+ child.type_keys('5x')
+ local exhausted_at_last = child.lua_get('vim.api.nvim_get_current_win() == _G.nav_count_wins[4]')
+ child.type_keys('5x')
+ local stayed_at_last = child.lua_get('vim.api.nvim_get_current_win() == _G.nav_count_wins[4]')
+ local delegations = child.lua_get('_G.nav_delegations')
+
+ expect.equality({
+ after_two = after_two,
+ exhausted_at_last = exhausted_at_last,
+ stayed_at_last = stayed_at_last,
+ delegations = delegations,
+ }, {
+ after_two = true,
+ exhausted_at_last = true,
+ stayed_at_last = true,
+ delegations = {},
+ })
+end
+
+T['delegates normal floats but not embedded floats'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ package.loaded['herdr-splits.herdr'] = {
+ focus_pane = function(direction)
+ calls[#calls + 1] = direction
+ return true
+ end,
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ local normal = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), true, {
+ relative = 'editor', row = 1, col = 1, width = 12, height = 3, zindex = 50,
+ })
+ nav.move_cursor('right')
+ local normal_stayed = vim.api.nvim_get_current_win() == normal
+
+ vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), true, {
+ relative = 'editor', row = 2, col = 2, width = 12, height = 3, zindex = 49,
+ })
+ nav.move_cursor('left')
+
+ return { calls = calls, normal_stayed = normal_stayed }
+ end)
+
+ expect.equality(result, {
+ calls = { 'right' },
+ normal_stayed = true,
+ })
+end
+
+T['applies no-session stop, wrap, and callback edge behavior'] = function()
+ local result = child.lua_func(function()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ local function horizontal_pair()
+ vim.cmd('only')
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(wins[1])
+ return wins
+ end
+
+ local stop_wins = horizontal_pair()
+ nav.move_cursor('left', { at_edge = 'stop' })
+ local stopped = vim.api.nvim_get_current_win() == stop_wins[1]
+
+ local wrap_wins = horizontal_pair()
+ nav.move_cursor('left', { at_edge = 'wrap' })
+ local wrapped = vim.api.nvim_get_current_win() == wrap_wins[2]
+
+ local callback_wins = horizontal_pair()
+ local callback = {}
+ nav.move_cursor('left', {
+ at_edge = function(ctx)
+ callback.direction = ctx.direction
+ callback.is_sidebar = ctx.is_sidebar
+ callback.has_split = type(ctx.split) == 'function'
+ callback.has_wrap = type(ctx.wrap) == 'function'
+ ctx.wrap()
+ end,
+ })
+ callback.wrapped = vim.api.nvim_get_current_win() == callback_wins[2]
+
+ return {
+ stopped = stopped,
+ wrapped = wrapped,
+ callback = callback,
+ }
+ end)
+
+ expect.equality(result, {
+ stopped = true,
+ wrapped = true,
+ callback = {
+ direction = 'left',
+ is_sidebar = false,
+ has_split = true,
+ has_wrap = true,
+ wrapped = true,
+ },
+ })
+end
+
+T['creates local edge splits and preserves split preferences'] = function()
+ local result = child.lua_func(function()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+ local cases = {}
+
+ local function exercise(direction, preference)
+ vim.cmd('only')
+ vim.o.splitright = preference
+ vim.o.splitbelow = preference
+ local before = #vim.api.nvim_tabpage_list_wins(0)
+ local original = vim.api.nvim_get_current_win()
+
+ nav.move_cursor(direction, { at_edge = 'split' })
+
+ local current = vim.api.nvim_get_current_win()
+ local original_position = vim.api.nvim_win_get_position(original)
+ local current_position = vim.api.nvim_win_get_position(current)
+ local placement_matches_preference
+ if direction == 'left' or direction == 'right' then
+ if preference then
+ placement_matches_preference = current_position[2] > original_position[2]
+ else
+ placement_matches_preference = current_position[2] < original_position[2]
+ end
+ elseif preference then
+ placement_matches_preference = current_position[1] > original_position[1]
+ else
+ placement_matches_preference = current_position[1] < original_position[1]
+ end
+
+ cases[#cases + 1] = {
+ direction = direction,
+ preference = preference,
+ created_one = #vim.api.nvim_tabpage_list_wins(0) == before + 1,
+ focused_new = current ~= original,
+ placement_matches_preference = placement_matches_preference,
+ splitright_restored = vim.o.splitright == preference,
+ splitbelow_restored = vim.o.splitbelow == preference,
+ }
+ end
+
+ for _, direction in ipairs({ 'left', 'right', 'up', 'down' }) do
+ exercise(direction, false)
+ exercise(direction, true)
+ end
+ return cases
+ end)
+
+ local expected = {}
+ for _, direction in ipairs({ 'left', 'right', 'up', 'down' }) do
+ for _, preference in ipairs({ false, true }) do
+ expected[#expected + 1] = {
+ direction = direction,
+ preference = preference,
+ created_one = true,
+ focused_new = true,
+ placement_matches_preference = true,
+ splitright_restored = true,
+ splitbelow_restored = true,
+ }
+ end
+ end
+ expect.equality(result, expected)
+end
+
+T['uses Herdr neighbors and falls back locally on focus or edge failures'] = function()
+ local result = child.lua_func(function()
+ local edge_queue = { false, false, '<nil>' }
+ local focus_queue = { true, false }
+ local calls = {}
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ unzoom_enabled = function() return false end,
+ current_pane_at_edge = function(direction)
+ calls[#calls + 1] = 'edge:' .. direction
+ local value = table.remove(edge_queue, 1)
+ if value == '<nil>' then return nil end
+ return value
+ end,
+ focus_pane = function(direction)
+ calls[#calls + 1] = 'focus:' .. direction
+ return table.remove(focus_queue, 1)
+ end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ local function at_left()
+ vim.cmd('only')
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(wins[1])
+ return wins
+ end
+
+ local success_wins = at_left()
+ nav.move_cursor('left')
+ local focus_success_stays = vim.api.nvim_get_current_win() == success_wins[1]
+
+ local failure_wins = at_left()
+ nav.move_cursor('left')
+ local focus_failure_wraps = vim.api.nvim_get_current_win() == failure_wins[2]
+
+ local unknown_wins = at_left()
+ nav.move_cursor('left')
+ local unknown_wraps = vim.api.nvim_get_current_win() == unknown_wins[2]
+
+ return {
+ focus_success_stays = focus_success_stays,
+ focus_failure_wraps = focus_failure_wraps,
+ unknown_wraps = unknown_wraps,
+ calls = calls,
+ }
+ end)
+
+ expect.equality(result, {
+ focus_success_stays = true,
+ focus_failure_wraps = true,
+ unknown_wraps = true,
+ calls = {
+ 'edge:left', 'focus:left',
+ 'edge:left', 'focus:left',
+ 'edge:left',
+ },
+ })
+end
+
+T['unzooms and retries native movement before querying Herdr edges'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ local start_win = vim.api.nvim_get_current_win()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ unzoom_enabled = function() return true end,
+ current_pane_is_zoomed = function()
+ calls[#calls + 1] = 'zoomed'
+ return true
+ end,
+ unzoom = function()
+ calls[#calls + 1] = 'unzoom'
+ local old_splitright = vim.o.splitright
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ vim.api.nvim_set_current_win(start_win)
+ vim.o.splitright = old_splitright
+ return true
+ end,
+ current_pane_at_edge = function()
+ calls[#calls + 1] = 'edge'
+ return false
+ end,
+ focus_pane = function()
+ calls[#calls + 1] = 'focus'
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ require('herdr-splits.nav').move_cursor('right')
+
+ return {
+ moved = vim.api.nvim_get_current_win() ~= start_win,
+ windows = #vim.api.nvim_tabpage_list_wins(0),
+ calls = calls,
+ }
+ end)
+
+ expect.equality(result, { moved = true, windows = 2, calls = { 'zoomed', 'unzoom' } })
+end
+
+T['wraps through the reverse Herdr neighbor unless nav_at_edge stops it'] = function()
+ local result = child.lua_func(function()
+ local mode = 'wrap'
+ local calls = {}
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ unzoom_enabled = function() return false end,
+ current_pane_at_edge = function(direction)
+ calls[#calls + 1] = 'edge:' .. direction
+ return direction == 'left'
+ end,
+ nav_at_edge = function() return mode end,
+ focus_pane = function(direction)
+ calls[#calls + 1] = 'focus:' .. direction
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ local function at_left()
+ vim.cmd('only')
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(wins[1])
+ return wins
+ end
+
+ local herdr_wrap_wins = at_left()
+ nav.move_cursor('left')
+ local herdr_wrap_stays = vim.api.nvim_get_current_win() == herdr_wrap_wins[1]
+
+ mode = 'stop'
+ local local_wrap_wins = at_left()
+ nav.move_cursor('left')
+ local local_wraps = vim.api.nvim_get_current_win() == local_wrap_wins[2]
+
+ return { herdr_wrap_stays = herdr_wrap_stays, local_wraps = local_wraps, calls = calls }
+ end)
+
+ expect.equality(result, {
+ herdr_wrap_stays = true,
+ local_wraps = true,
+ calls = { 'edge:left', 'edge:right', 'focus:right', 'edge:left' },
+ })
+end
+
+T['command-line window delegates to Herdr at the screen edge and never wincmds'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ local edge = false
+ local nav_mode = 'wrap'
+ package.loaded['herdr-splits.win'] = {
+ is_command_line_window = function() return true end,
+ is_floating = function() return false end,
+ is_embedded_floating_window = function() return false end,
+ is_ignored_or_preview = function() return false end,
+ dir_keys = { left = 'h', right = 'l', up = 'k', down = 'j' },
+ dir_keys_reverse = { left = 'l', right = 'h', up = 'j', down = 'k' },
+ reverse_direction = { left = 'right', right = 'left', up = 'down', down = 'up' },
+ }
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ current_pane_at_edge = function(direction)
+ calls[#calls + 1] = 'edge:' .. direction
+ return edge
+ end,
+ nav_at_edge = function() return nav_mode end,
+ focus_pane = function(direction)
+ calls[#calls + 1] = 'focus:' .. direction
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ -- single window: will_wrap is true in every direction, mirroring the
+ -- full-width/bottom command-line window geometry.
+ vim.cmd('only')
+
+ edge = false
+ nav.move_cursor('left')
+ local c1 = vim.deepcopy(calls); calls = {}
+
+ edge = true
+ nav_mode = 'wrap'
+ nav.move_cursor('right')
+ local c2 = vim.deepcopy(calls); calls = {}
+
+ nav_mode = 'stop'
+ nav.move_cursor('right')
+ local c3 = vim.deepcopy(calls)
+
+ return { neighbor = c1, wrap_reverse = c2, stop_noop = c3 }
+ end)
+
+ expect.equality(result, {
+ neighbor = { 'edge:left', 'focus:left' },
+ wrap_reverse = { 'edge:right', 'focus:left' },
+ stop_noop = { 'edge:right' },
+ })
+end
+
+T['command-line window no-ops silently when not at a screen edge'] = function()
+ local result = child.lua_func(function()
+ package.loaded['herdr-splits.win'] = {
+ is_command_line_window = function() return true end,
+ is_floating = function() return false end,
+ is_embedded_floating_window = function() return false end,
+ is_ignored_or_preview = function() return false end,
+ dir_keys = { left = 'h', right = 'l', up = 'k', down = 'j' },
+ dir_keys_reverse = { left = 'l', right = 'h', up = 'j', down = 'k' },
+ reverse_direction = { left = 'right', right = 'left', up = 'down', down = 'up' },
+ }
+ local focused = {}
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ current_pane_at_edge = function() return false end,
+ nav_at_edge = function() return 'wrap' end,
+ focus_pane = function(d) focused[#focused + 1] = d; return true end,
+ }
+ package.loaded['herdr-splits.nav'] = nil
+ local nav = require('herdr-splits.nav')
+
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ -- left window: 'right' has a Neovim neighbor -> will_wrap == false
+ vim.api.nvim_set_current_win(wins[1])
+ nav.move_cursor('right')
+ return { stayed = vim.api.nvim_get_current_win() == wins[1], focused = focused }
+ end)
+
+ -- Without the guard, wincmd l would move to wins[2]; with it, silent no-op.
+ expect.equality(result, { stayed = true, focused = {} })
+end
+
+return T
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_plugin.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_plugin.lua
new file mode 100644
index 0000000..0afc929
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_plugin.lua
@@ -0,0 +1,60 @@
+local T
+local expect = MiniTest.expect
+local child = MiniTest.new_child_neovim()
+
+local source = debug.getinfo(1, 'S').source
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+
+T = MiniTest.new_set({
+ hooks = {
+ pre_case = function()
+ child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
+ child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
+ end,
+ post_case = function()
+ child.stop()
+ end,
+ },
+})
+
+T['public module loads its expected API'] = function()
+ local result = child.lua_func(function()
+ local plugin = require('herdr-splits')
+ local names = {
+ 'setup',
+ 'sync_herdr',
+ 'resize_left',
+ 'resize_down',
+ 'resize_up',
+ 'resize_right',
+ 'move_cursor_left',
+ 'move_cursor_down',
+ 'move_cursor_up',
+ 'move_cursor_right',
+ 'add_ignored_filetype',
+ 'add_ignored_buftype',
+ }
+ local types = {}
+ for _, name in ipairs(names) do
+ types[name] = type(plugin[name])
+ end
+ return types
+ end)
+
+ expect.equality(result, {
+ setup = 'function',
+ sync_herdr = 'function',
+ resize_left = 'function',
+ resize_down = 'function',
+ resize_up = 'function',
+ resize_right = 'function',
+ move_cursor_left = 'function',
+ move_cursor_down = 'function',
+ move_cursor_up = 'function',
+ move_cursor_right = 'function',
+ add_ignored_filetype = 'function',
+ add_ignored_buftype = 'function',
+ })
+end
+
+return T
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_resize.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_resize.lua
new file mode 100644
index 0000000..0053377
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_resize.lua
@@ -0,0 +1,302 @@
+local T
+local expect = MiniTest.expect
+local child = MiniTest.new_child_neovim()
+
+local source = debug.getinfo(1, 'S').source
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+
+T = MiniTest.new_set({
+ hooks = {
+ pre_case = function()
+ child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
+ child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
+ end,
+ post_case = function()
+ child.stop()
+ end,
+ },
+})
+
+T['uses the correct horizontal resize sign at both sides'] = function()
+ local deltas = child.lua_func(function()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local resize = require('herdr-splits.resize')
+ vim.o.equalalways = false
+ vim.o.winminwidth = 1
+
+ local function delta(side, direction)
+ vim.cmd('only')
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(wins[side == 'left' and 1 or 2])
+ local before = vim.api.nvim_win_get_width(0)
+ resize.resize(direction, 2)
+ return vim.api.nvim_win_get_width(0) - before
+ end
+
+ return {
+ left_left = delta('left', 'left'),
+ left_right = delta('left', 'right'),
+ right_left = delta('right', 'left'),
+ right_right = delta('right', 'right'),
+ }
+ end)
+
+ expect.equality(deltas, { left_left = -2, left_right = 2, right_left = 2, right_right = -2 })
+end
+
+T['uses the correct vertical resize sign at top and bottom'] = function()
+ local deltas = child.lua_func(function()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local resize = require('herdr-splits.resize')
+ vim.o.equalalways = false
+ vim.o.winminheight = 1
+
+ local function delta(side, direction)
+ vim.cmd('only')
+ vim.o.splitbelow = true
+ vim.cmd('split')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[1] < vim.api.nvim_win_get_position(b)[1]
+ end)
+ vim.api.nvim_set_current_win(wins[side == 'top' and 1 or 2])
+ local before = vim.api.nvim_win_get_height(0)
+ resize.resize(direction, 2)
+ return vim.api.nvim_win_get_height(0) - before
+ end
+
+ return {
+ top_up = delta('top', 'up'),
+ top_down = delta('top', 'down'),
+ bottom_up = delta('bottom', 'up'),
+ bottom_down = delta('bottom', 'down'),
+ }
+ end)
+
+ expect.equality(deltas, { top_up = -2, top_down = 2, bottom_up = 2, bottom_down = -2 })
+end
+
+T['floors explicit cell amounts and clamps nonpositive values'] = function()
+ local deltas = child.lua_func(function()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local resize = require('herdr-splits.resize')
+ vim.o.equalalways = false
+ vim.o.winminwidth = 1
+
+ local function delta(amount)
+ vim.cmd('only')
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(wins[1])
+ local before = vim.api.nvim_win_get_width(0)
+ resize.resize('right', amount)
+ return vim.api.nvim_win_get_width(0) - before
+ end
+
+ return { floored = delta(2.9), zero = delta(0), negative = delta(-4) }
+ end)
+
+ expect.equality(deltas, { floored = 2, zero = 1, negative = 1 })
+end
+
+T['multiplies native resize cells by the typed count'] = function()
+ local before = child.lua_func(function()
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return false end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local config = require('herdr-splits.config')
+ config.neovim_amount = 2
+ vim.o.equalalways = false
+ vim.o.winminwidth = 1
+ vim.o.splitright = true
+ vim.cmd('vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+ vim.api.nvim_set_current_win(wins[1])
+ vim.keymap.set('n', 'x', function()
+ require('herdr-splits.resize').resize('right')
+ end)
+ return vim.api.nvim_win_get_width(0)
+ end)
+
+ child.type_keys('3x')
+ local after = child.lua_get('vim.api.nvim_win_get_width(0)')
+ expect.equality(after - before, 6)
+end
+
+T['delegates full-terminal ratios for defaults, counts, and explicit amounts'] = function()
+ child.lua_func(function()
+ _G.resize_calls = {}
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ current_pane_is_zoomed = function() return false end,
+ resize_pane = function(direction, amount)
+ _G.resize_calls[#_G.resize_calls + 1] = { direction, amount }
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local config = require('herdr-splits.config')
+ config.default_amount = 0.03
+ local resize = require('herdr-splits.resize')
+ resize.resize('left')
+ resize.resize('right', 0.2)
+ resize.resize('left', 2)
+ vim.keymap.set('n', 'x', function() resize.resize('right') end)
+ end)
+
+ child.type_keys('3x')
+ local calls = child.lua_get('_G.resize_calls')
+ expect.equality(calls, {
+ { 'left', 0.03 },
+ { 'right', 0.2 },
+ { 'left', 0.03 },
+ { 'right', 0.09 },
+ })
+end
+
+T['delegates vertical full-terminal ratios using real geometry'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ current_pane_is_zoomed = function() return false end,
+ resize_pane = function(direction, amount)
+ calls[#calls + 1] = { direction, amount }
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local config = require('herdr-splits.config')
+ config.default_amount = 0.03
+ local win = require('herdr-splits.win')
+ local resize = require('herdr-splits.resize')
+
+ local geometry = {
+ full_height = win.is_full_height(),
+ at_top = win.at_top_edge(),
+ at_bottom = win.at_bottom_edge(),
+ }
+ resize.resize('up')
+ resize.resize('down', 0.2)
+ return { geometry = geometry, calls = calls }
+ end)
+
+ expect.equality(result, {
+ geometry = { full_height = true, at_top = true, at_bottom = true },
+ calls = { { 'up', 0.03 }, { 'down', 0.2 } },
+ })
+end
+
+T['delegates normal floats but suppresses embedded, sidebar, and zoomed windows'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ local zoomed = false
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ current_pane_is_zoomed = function() return zoomed end,
+ resize_pane = function(direction, amount)
+ calls[#calls + 1] = { direction, amount }
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local resize = require('herdr-splits.resize')
+ local base = vim.api.nvim_get_current_win()
+
+ local normal = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), true, {
+ relative = 'editor', row = 1, col = 1, width = 12, height = 3, zindex = 50,
+ })
+ resize.resize('right', 0.15)
+
+ local embedded = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), true, {
+ relative = 'editor', row = 2, col = 2, width = 12, height = 3, zindex = 49,
+ })
+ resize.resize('left')
+
+ vim.api.nvim_set_current_win(base)
+ vim.api.nvim_win_close(embedded, true)
+ vim.api.nvim_win_close(normal, true)
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr })
+ resize.resize('left')
+
+ vim.api.nvim_set_option_value('buftype', '', { buf = bufnr })
+ zoomed = true
+ resize.resize('right')
+
+ return { calls = calls, current = vim.api.nvim_get_current_win() == base }
+ end)
+
+ expect.equality(result, { calls = { { 'right', 0.15 } }, current = true })
+end
+
+T['command-line window horizontal resize delegates past the nofile sidebar rule'] = function()
+ local result = child.lua_func(function()
+ local calls = {}
+ local cmdwin = true
+ package.loaded['herdr-splits.win'] = {
+ is_command_line_window = function() return cmdwin end,
+ is_embedded_floating_window = function() return false end,
+ is_floating = function() return false end,
+ is_ignored_or_preview = function() return true end,
+ is_full_width = function() return true end,
+ is_full_height = function() return false end,
+ at_left_edge = function() return true end,
+ at_right_edge = function() return true end,
+ at_top_edge = function() return true end,
+ at_bottom_edge = function() return true end,
+ win_position = function() return 'start' end,
+ }
+ package.loaded['herdr-splits.herdr'] = {
+ is_in_session = function() return true end,
+ current_pane_is_zoomed = function() return false end,
+ resize_pane = function(direction, amount)
+ calls[#calls + 1] = { direction, amount }
+ return true
+ end,
+ }
+ package.loaded['herdr-splits.resize'] = nil
+ local config = require('herdr-splits.config')
+ config.default_amount = 0.03
+ local resize = require('herdr-splits.resize')
+
+ -- cmdwin + full-width + nofile: horizontal resize delegates to Herdr
+ cmdwin = true
+ resize.resize('left')
+ resize.resize('right', 0.2)
+
+ -- ordinary nofile buffer (not cmdwin): sidebar rule still blocks delegation
+ cmdwin = false
+ resize.resize('left')
+
+ return { calls = calls }
+ end)
+
+ expect.equality(result, {
+ calls = { { 'left', 0.03 }, { 'right', 0.2 } },
+ })
+end
+
+return T
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_win.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_win.lua
new file mode 100644
index 0000000..c8312fd
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_win.lua
@@ -0,0 +1,138 @@
+local T
+local expect = MiniTest.expect
+local child = MiniTest.new_child_neovim()
+
+local source = debug.getinfo(1, 'S').source
+local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
+
+T = MiniTest.new_set({
+ hooks = {
+ pre_case = function()
+ child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
+ child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
+ end,
+ post_case = function()
+ child.stop()
+ end,
+ },
+})
+
+T['win_position identifies horizontal split positions'] = function()
+ local positions = child.lua_func(function()
+ vim.cmd('vsplit | vsplit')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
+ end)
+
+ local out = {}
+ for _, win in ipairs(wins) do
+ vim.api.nvim_set_current_win(win)
+ out[#out + 1] = require('herdr-splits.win').win_position('left')
+ end
+ return out
+ end)
+
+ expect.equality(positions, { 'start', 'middle', 'last' })
+end
+
+T['win_position identifies vertical split positions'] = function()
+ local positions = child.lua_func(function()
+ vim.cmd('split | split')
+ local wins = vim.api.nvim_tabpage_list_wins(0)
+ table.sort(wins, function(a, b)
+ return vim.api.nvim_win_get_position(a)[1] < vim.api.nvim_win_get_position(b)[1]
+ end)
+
+ local out = {}
+ for _, win in ipairs(wins) do
+ vim.api.nvim_set_current_win(win)
+ out[#out + 1] = require('herdr-splits.win').win_position('up')
+ end
+ return out
+ end)
+
+ expect.equality(positions, { 'start', 'middle', 'last' })
+end
+
+T['floating classification distinguishes embedded floats'] = function()
+ local result = child.lua_func(function()
+ local win = require('herdr-splits.win')
+ local normal = vim.api.nvim_get_current_win()
+ local low = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), false, {
+ relative = 'editor', row = 1, col = 1, width = 10, height = 3, zindex = 49,
+ })
+ local default = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), false, {
+ relative = 'editor', row = 2, col = 2, width = 10, height = 3, zindex = 50,
+ })
+
+ return {
+ normal_floating = win.is_floating(normal),
+ normal_embedded = win.is_embedded_floating_window(normal),
+ low_floating = win.is_floating(low),
+ low_embedded = win.is_embedded_floating_window(low),
+ default_floating = win.is_floating(default),
+ default_embedded = win.is_embedded_floating_window(default),
+ }
+ end)
+
+ expect.equality(result, {
+ normal_floating = false,
+ normal_embedded = false,
+ low_floating = true,
+ low_embedded = true,
+ default_floating = true,
+ default_embedded = false,
+ })
+end
+
+T['ignored window classification uses buffer type and filetype'] = function()
+ local result = child.lua_func(function()
+ local win = require('herdr-splits.win')
+ local bufnr = vim.api.nvim_get_current_buf()
+ local regular = win.is_ignored_win()
+
+ vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr })
+ local ignored_buftype = win.is_ignored_win()
+ vim.api.nvim_set_option_value('buftype', '', { buf = bufnr })
+
+ vim.api.nvim_set_option_value('filetype', 'neo-tree', { buf = bufnr })
+ local ignored_filetype = win.is_ignored_win()
+ return {
+ regular = regular,
+ ignored_buftype = ignored_buftype,
+ ignored_filetype = ignored_filetype,
+ }
+ end)
+
+ expect.equality(result, {
+ regular = false,
+ ignored_buftype = true,
+ ignored_filetype = true,
+ })
+end
+
+T['preview windows are ignored only when configured'] = function()
+ local result = child.lua_func(function()
+ local win = require('herdr-splits.win')
+ local config = require('herdr-splits.config')
+ vim.wo.previewwindow = true
+
+ config.ignore_previewwindows = false
+ local disabled = win.is_ignored_or_preview()
+ config.ignore_previewwindows = true
+ local enabled = win.is_ignored_or_preview()
+ return { disabled = disabled, enabled = enabled }
+ end)
+
+ expect.equality(result, { disabled = false, enabled = true })
+end
+
+T['command-line window is not active in a normal buffer'] = function()
+ local result = child.lua_func(function()
+ return require('herdr-splits.win').is_command_line_window()
+ end)
+ expect.equality(result, false)
+end
+
+return T
diff --git a/dot_config/herdr/session.json b/dot_config/herdr/session.json
new file mode 100644
index 0000000..fa51fc2
--- /dev/null
+++ b/dot_config/herdr/session.json
@@ -0,0 +1,40 @@
+{
+ "version": 3,
+ "workspaces": [
+ {
+ "id": "w5",
+ "custom_name": null,
+ "identity_cwd": "/Users/zamp",
+ "public_pane_numbers": {
+ "17": 1
+ },
+ "next_public_pane_number": 2,
+ "public_tab_numbers": [
+ 1
+ ],
+ "next_public_tab_number": 2,
+ "tabs": [
+ {
+ "custom_name": null,
+ "layout": {
+ "Pane": 17
+ },
+ "panes": {
+ "17": {
+ "cwd": "/Users/zamp"
+ }
+ },
+ "zoomed": false,
+ "focused": 17,
+ "root_pane": 17
+ }
+ ],
+ "active_tab": 0
+ }
+ ],
+ "active": 0,
+ "selected": 0,
+ "sidebar_width": 28,
+ "sidebar_section_split": 0.5081967,
+ "collapsed_space_keys": []
+} \ No newline at end of file