diff --git a/README.md b/README.md index 261e362..ee3f112 100644 --- a/README.md +++ b/README.md @@ -404,8 +404,9 @@ commands you trust. Squarebox builds on Omarchy's defaults with one portable rule: `Super` belongs to the OS/window manager, while `Ctrl` acts immediately in the focused app. -Adding `Shift` modifies or moves the target. Herdr follows this language -directly; its `F12` prefix remains available as a compatibility fallback. +Herdr uses `Ctrl+Alt` for direct letter chords because many terminals collapse +`Ctrl+Shift+letter` into the unshifted control key. Its `F12` prefix remains +available as a compatibility fallback. Herdr's generated default uses familiar app shortcuts: @@ -415,8 +416,8 @@ Herdr's generated default uses familiar app shortcuts: | Cycle / select tabs | `Ctrl+Tab`, `Ctrl+Shift+Tab` / `Ctrl+1-9` | | Focus / swap pane | `Ctrl+Arrow` / `Ctrl+Shift+Arrow` | | Sidebar / go to | `Ctrl+B` / `Ctrl+G` | -| Split right / down | `Ctrl+\\` / `Ctrl+Shift+\\` | -| Zoom / new workspace | `Ctrl+Shift+Z` / `Ctrl+Shift+N` | +| Split right / down | `Ctrl+\\` / `Ctrl+Alt+\\` | +| Zoom / new workspace | `Ctrl+Alt+Z` / `Ctrl+Alt+N` | | Compatibility prefix | `F12` | These bindings intentionally take precedence over matching shell or nested-app diff --git a/setup.sh b/setup.sh index db89a27..2770b2b 100755 --- a/setup.sh +++ b/setup.sh @@ -1788,14 +1788,15 @@ _install_herdr_inner() { agent_panel_sort = "spaces" # Super belongs to the OS/window manager. Ctrl acts immediately in Herdr; - # Shift modifies or moves. F12 keeps the upstream-style prefix fallbacks. + # Ctrl+Alt keeps direct letter chords distinguishable across terminals. + # F12 keeps the upstream-style prefix fallbacks. [keys] prefix = "f12" help = ["ctrl+?", "prefix+?"] goto = ["ctrl+g", "prefix+g"] workspace_picker = "prefix+w" toggle_sidebar = ["ctrl+b", "prefix+b"] - detach = ["ctrl+shift+q", "prefix+q"] + detach = ["ctrl+alt+q", "prefix+q"] new_tab = ["ctrl+t", "prefix+c"] previous_tab = ["ctrl+shift+tab", "prefix+p"] @@ -1804,7 +1805,7 @@ _install_herdr_inner() { rename_tab = "prefix+shift+t" close_tab = "prefix+shift+x" - new_workspace = ["ctrl+shift+n", "prefix+shift+n"] + new_workspace = ["ctrl+alt+n", "prefix+shift+n"] rename_workspace = "prefix+shift+w" close_workspace = "prefix+shift+d" @@ -1819,12 +1820,17 @@ _install_herdr_inner() { close_pane = ["ctrl+w", "prefix+x"] split_vertical = ["ctrl+backslash", "prefix+v"] - split_horizontal = ["ctrl+shift+backslash", "prefix+minus"] - zoom = ["ctrl+shift+z", "prefix+z"] + split_horizontal = ["ctrl+alt+backslash", "prefix+minus"] + zoom = ["ctrl+alt+z", "prefix+z"] resize_mode = "prefix+r" - HERDRCONF + HERDRCONF mv -fT -- "$config_tmp" "$config_path" || exit 1 config_tmp="" + if ! herdr config check >/dev/null 2>&1; then + echo "ERROR: Generated Herdr config failed validation: $config_path" >&2 + rm -f -- "$config_path" + exit 1 + fi ); then return 1 fi diff --git a/tests/test-provision-state.sh b/tests/test-provision-state.sh index 57c710a..d978e1d 100755 --- a/tests/test-provision-state.sh +++ b/tests/test-provision-state.sh @@ -258,16 +258,38 @@ rm -f "$ZELLIJ_CONF" # Herdr uses the shared immediate-app keyboard language for a fresh Managed # home, while an existing config remains user-controlled. -printf '#!/usr/bin/env bash\nexit 0\n' > "$BIN/herdr" +cat > "$BIN/herdr" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +if [ "${1:-}" = config ] && [ "${2:-}" = check ]; then + config="$HOME/.config/herdr/config.toml" + grep -Fqx 'detach = ["ctrl+alt+q", "prefix+q"]' "$config" + grep -Fqx 'new_workspace = ["ctrl+alt+n", "prefix+shift+n"]' "$config" + grep -Fqx 'split_horizontal = ["ctrl+alt+backslash", "prefix+minus"]' "$config" + grep -Fqx 'zoom = ["ctrl+alt+z", "prefix+z"]' "$config" + [ "${HERDR_CHECK_FAIL:-0}" -eq 0 ] + printf 'config check\n' >> "$HERDR_TEST_LOG" +fi +EOF chmod +x "$BIN/herdr" printf 'herdr\n' > "$STATE/multiplexer" rm -rf "$HOME_DIR/.config/herdr" -HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \ +HERDR_TEST_LOG="$TMP/herdr-check.log" HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \ SQUAREBOX_TOOL_LIB="$FIXTURE_LIB" SQUAREBOX_TOOLS_YAML=/dev/null \ PATH="$BIN:$PATH" bash "$ROOT/setup.sh" --rerun multiplexers >"$TMP/herdr-fresh.out" 2>"$TMP/herdr-fresh.err" HERDR_CONF="$HOME_DIR/.config/herdr/config.toml" -assert_true "grep -Fqx 'prefix = \"f12\"' '$HERDR_CONF' && grep -Fqx 'new_tab = [\"ctrl+t\", \"prefix+c\"]' '$HERDR_CONF' && grep -Fqx 'focus_pane_left = [\"ctrl+left\", \"prefix+h\"]' '$HERDR_CONF'" \ - "fresh Herdr config uses immediate Ctrl actions with F12 fallbacks" +assert_true "grep -Fqx 'config check' '$TMP/herdr-check.log' && grep -Fqx 'prefix = \"f12\"' '$HERDR_CONF' && grep -Fqx 'new_tab = [\"ctrl+t\", \"prefix+c\"]' '$HERDR_CONF' && grep -Fqx 'focus_pane_left = [\"ctrl+left\", \"prefix+h\"]' '$HERDR_CONF'" \ + "fresh Herdr config passes validation with terminal-safe direct chords and F12 fallbacks" +rm -f "$HERDR_CONF" +if HERDR_CHECK_FAIL=1 HERDR_TEST_LOG="$TMP/herdr-check-fail.log" HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \ + SQUAREBOX_TOOL_LIB="$FIXTURE_LIB" SQUAREBOX_TOOLS_YAML=/dev/null \ + PATH="$BIN:$PATH" bash "$ROOT/setup.sh" --rerun multiplexers >"$TMP/herdr-invalid.out" 2>"$TMP/herdr-invalid.err"; then + HERDR_INVALID_RC=0 +else + HERDR_INVALID_RC=$? +fi +assert_true "[ \"$HERDR_INVALID_RC\" -ne 0 ] && [ ! -e '$HERDR_CONF' ] && grep -Fq 'Generated Herdr config failed validation' '$TMP/herdr-invalid.err'" \ + "Herdr setup removes a generated config that fails validation" printf '# user-owned Herdr config\n' > "$HERDR_CONF" HOME="$HOME_DIR" SQUAREBOX_STATE_DIR="$STATE" \ SQUAREBOX_TOOL_LIB="$FIXTURE_LIB" SQUAREBOX_TOOLS_YAML=/dev/null \