From beb09b3a7c8119386524dd365ef76e19f3180229 Mon Sep 17 00:00:00 2001 From: Jarod Taylor Date: Mon, 8 Jun 2026 16:23:32 -0500 Subject: [PATCH 1/5] chore: gitignore local planning docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep brainstorm/plan artifacts (docs/brainstorms/) local — agent-session scratch, not something to publish in a public dotfiles repo. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b149de9..d4ab119 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ /docs/superpowers/ /docs/ideation/ /docs/plans/ +/docs/brainstorms/ # Sketchybar helper build artifacts. These are compiled by `make` when # `run_onchange_after_40-sketchybar-deps.sh.tmpl` runs on each machine — From 10d7a587fb24b69811fff0ec4c1b9c999d73effb Mon Sep 17 00:00:00 2001 From: Jarod Taylor Date: Mon, 8 Jun 2026 16:23:32 -0500 Subject: [PATCH 2/5] feat(tmux): LazyVim-cohesive remote-first tmux config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greenfield tmux.conf that makes tmux the terminal-layer of Neovim and a durable cross-device workspace: C-Space prefix, Ctrl-hjkl into Neovim splits, OSC52 clipboard over SSH, tmux-menus discoverability, catppuccin theme, and resurrect+continuum persistence. Plugins are vendored via chezmoiexternal to ~/.config/tmux/plugins/ — a path the plain VPS repo can populate too. catppuccin is pinned to v2.3.0 (it ships breaking syntax); the rest track unpinned. Load order catppuccin→resurrect→ continuum is load-bearing — continuum prepends its autosave hook to status-right, which catppuccin sets. --- home/.chezmoiexternal.toml | 38 ++++++++++++ home/dot_config/tmux/tmux.conf | 99 +++++++++++++++++++++++++++++++ home/dot_config/zsh/abbreviations | 4 ++ 3 files changed, 141 insertions(+) diff --git a/home/.chezmoiexternal.toml b/home/.chezmoiexternal.toml index 687b18a..d507e66 100644 --- a/home/.chezmoiexternal.toml +++ b/home/.chezmoiexternal.toml @@ -2,3 +2,41 @@ type = "git-repo" url = "https://github.com/catppuccin/zsh-syntax-highlighting.git" refreshPeriod = "168h" + +# --- tmux plugins (vendored; sourced from dot_config/tmux/tmux.conf) --- +# Vendored to ~/.config/tmux/plugins// so one tmux.conf works under +# chezmoi here and a plain dotfiles repo on the VPS (no chezmoi-only paths +# in the config). vim-tmux-navigator is intentionally NOT vendored — its +# tmux side is a handful of stable bind-key lines written inline in tmux.conf. + +# catppuccin/tmux — PINNED to v2.3.0 (it shipped breaking v2 syntax, so a +# moving target would break the status line). The key matches catppuccin's +# upstream clone path (repo root holds catppuccin.tmux under tmux/), so +# `run ~/.config/tmux/plugins/catppuccin/tmux/catppuccin.tmux` resolves. +# Pinned entries omit refreshPeriod — the shallow tag clone is frozen. +[".config/tmux/plugins/catppuccin/tmux"] +type = "git-repo" +url = "https://github.com/catppuccin/tmux.git" + +[".config/tmux/plugins/catppuccin/tmux".clone] +args = ["--branch", "v2.3.0", "--depth", "1"] + +# tmux-menus — discoverability popup (pure POSIX/tmux script, no build step, +# no Python, no submodules). Unpinned: stable and self-contained. +[".config/tmux/plugins/tmux-menus"] +type = "git-repo" +url = "https://github.com/jaclu/tmux-menus.git" +refreshPeriod = "168h" + +# tmux-resurrect — save/restore sessions, windows, panes across reboots. +[".config/tmux/plugins/tmux-resurrect"] +type = "git-repo" +url = "https://github.com/tmux-plugins/tmux-resurrect.git" +refreshPeriod = "168h" + +# tmux-continuum — automatic save + restore-on-start. MUST be sourced last in +# tmux.conf (after catppuccin) so its status-right save hook is not overwritten. +[".config/tmux/plugins/tmux-continuum"] +type = "git-repo" +url = "https://github.com/tmux-plugins/tmux-continuum.git" +refreshPeriod = "168h" diff --git a/home/dot_config/tmux/tmux.conf b/home/dot_config/tmux/tmux.conf index e69de29..419a98d 100644 --- a/home/dot_config/tmux/tmux.conf +++ b/home/dot_config/tmux/tmux.conf @@ -0,0 +1,99 @@ +# ~/.config/tmux/tmux.conf — LazyVim-cohesive, remote-first tmux +# +# Mental model: tmux is a durable, per-host workspace. Sessions live on the +# host (M5 / Mac Mini / VPS); your terminals (Ghostty, Termius) are viewports. +# Detach and the work keeps running; reattach from any device and it's there. +# +# Prefix is C-Space (Ctrl+Space) — the terminal-layer sibling of Neovim's +# Space leader. Forgot a binding? Press it, then Space, for the menu (tmux-menus, +# wired up below). hjkl moves panes; C-hjkl crosses into Neovim splits. + +# ── Prefix ─────────────────────────────────────────────────────────────────── +unbind C-b # drop the default prefix +set -g prefix C-Space # leader = Ctrl+Space +bind C-Space send-prefix # press it twice to reach a nested (e.g. SSH) tmux + +# ── Correctness / terminal fidelity ────────────────────────────────────────── +# tmux-256color advertises italics; truecolor + undercurl are NOT in the base +# terminfo on any platform, so we add them as capability overrides below. +set -g default-terminal "tmux-256color" +set -as terminal-features ",xterm-256color:RGB" # truecolor for Neovim +set -as terminal-features ",xterm-256color:clipboard" # OSC52 capability +# undercurl + colored underlines (LSP diagnostics in Neovim) +set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm' +set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m' + +set -s set-clipboard on # accept OSC52 from inner apps (Neovim 0.10+ yank) +set -g allow-passthrough on # let Neovim's DCS-wrapped OSC52 reach the terminal +set -g escape-time 0 # kill the ESC lag that plagues Neovim in insert mode +set -g focus-events on # forward focus to Neovim (autoread, etc.) + +# ── Ergonomics ─────────────────────────────────────────────────────────────── +set -g mouse on +set -g base-index 1 # windows start at 1 (0 is a long thumb reach) +setw -g pane-base-index 1 # panes too +set -g renumber-windows on # close a window → no gaps in the numbering + +# ── Copy mode (vim idioms) ─────────────────────────────────────────────────── +# v to start a selection, y to yank — yank rides OSC52 to your LOCAL clipboard, +# even over SSH. (Pasting INTO tmux uses the terminal's own paste; that's a tmux +# limitation, not a bug.) +setw -g mode-keys vi +bind -T copy-mode-vi v send-keys -X begin-selection +bind -T copy-mode-vi y send-keys -X copy-selection-and-cancel + +# ── Seamless nav: vim-tmux-navigator (tmux side, written inline) ───────────── +# C-h/j/k/l moves between tmux panes AND Neovim splits with the same keys and no +# prefix. The is_vim check asks "is the focused pane running Neovim?" — if so the +# keys pass through to Neovim; otherwise tmux moves the pane. (Neovim side is the +# christoomey/vim-tmux-navigator plugin spec.) +vim_pattern='(\S+/)?g?\.?(view|l?n?vim?x?|fzf)(diff)?(-wrapped)?' +is_vim="ps -o state= -o comm= -t '#{pane_tty}' \ + | grep -iqE '^[^TXZ ]+ +${vim_pattern}$'" +bind -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L' +bind -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D' +bind -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U' +bind -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R' +bind -n 'C-\' if-shell "$is_vim" 'send-keys C-\\' 'select-pane -l' # last pane + +# ── Mnemonic bindings (small on purpose — the menu surfaces the rest) ──────── +# Splits open in the CURRENT pane's directory, so a new pane lands where you are. +bind | split-window -h -c "#{pane_current_path}" # | looks like a vertical wall +bind - split-window -v -c "#{pane_current_path}" # - looks like a horizontal one +bind c new-window -c "#{pane_current_path}" +bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reloaded" +# zoom a pane to fullscreen is prefix-z (built in); repeat to unzoom. + +# ═══ Plugins ═════════════════════════════════════════════════════════════════ +# Vendored to ~/.config/tmux/plugins/ by .chezmoiexternal.toml. The LOAD ORDER +# below is load-bearing: continuum PREPENDS its autosave hook to status-right, +# and catppuccin SETS status-right — so catppuccin must load before continuum or +# the autosave is silently lost. Order: menus → catppuccin → resurrect → continuum. + +# tmux-menus — press Space for a popup menu of tmux actions and their +# keys (the discoverability layer). Pure tmux script; nothing to build. +set -g @menus_trigger 'Space' +run-shell ~/.config/tmux/plugins/tmux-menus/menus.tmux + +# catppuccin (mocha) — theme + status line. Built-in modules only; cpu/ram/battery +# would need companion plugins (deferred). Sourced before resurrect/continuum. +set -g @catppuccin_flavor "mocha" +set -g @catppuccin_window_status_style "rounded" +run ~/.config/tmux/plugins/catppuccin/tmux/catppuccin.tmux +set -g status-right-length 100 +set -g status-left-length 100 +set -g status-left "" +set -g status-right "#{E:@catppuccin_status_application}" +set -agF status-right "#{E:@catppuccin_status_session}" +set -agF status-right "#{E:@catppuccin_status_date_time}" + +# tmux-resurrect — save/restore sessions. Restore Neovim sessions too (needs +# tpope/vim-obsession on the Neovim side, installed via lazy.nvim). +set -g @resurrect-strategy-nvim 'session' +run-shell ~/.config/tmux/plugins/tmux-resurrect/resurrect.tmux + +# tmux-continuum — auto-save every 15 min + restore on tmux start. MUST be the +# last plugin sourced (see the load-order note above). +set -g @continuum-restore 'on' +set -g @continuum-save-interval '15' +run-shell ~/.config/tmux/plugins/tmux-continuum/continuum.tmux diff --git a/home/dot_config/zsh/abbreviations b/home/dot_config/zsh/abbreviations index e858adb..9d60e47 100644 --- a/home/dot_config/zsh/abbreviations +++ b/home/dot_config/zsh/abbreviations @@ -74,3 +74,7 @@ abbr rgfzf="rg --line-number --no-heading . | fzf --ansi" abbr fdfzf="fd --type f | fzf --preview 'bat --color=always {}'" +# tmux — attach to (or create) the per-host "main" session. -d detaches any other +# viewport so it takes over full-size when you hop devices (no screen clamping). +abbr ta="tmux attach -d -t main || tmux new -s main" + From 9f543064f3696457f205308637cfab7de63c59ef Mon Sep 17 00:00:00 2001 From: Jarod Taylor Date: Mon, 8 Jun 2026 16:23:33 -0500 Subject: [PATCH 3/5] feat(nvim): tmux-navigator + obsession; free C-Space for the tmux prefix vim-tmux-navigator completes the seamless Ctrl-hjkl seam between Neovim splits and tmux panes; vim-obsession keeps a Session.vim so tmux-resurrect can restore the Neovim session after a reboot. blink.cmp's is freed since the tmux prefix now owns that chord. --- home/dot_config/nvim/lua/plugins/blink.lua | 14 ++++++++++ home/dot_config/nvim/lua/plugins/editor.lua | 29 +++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 home/dot_config/nvim/lua/plugins/blink.lua create mode 100644 home/dot_config/nvim/lua/plugins/editor.lua diff --git a/home/dot_config/nvim/lua/plugins/blink.lua b/home/dot_config/nvim/lua/plugins/blink.lua new file mode 100644 index 0000000..8d5b9cd --- /dev/null +++ b/home/dot_config/nvim/lua/plugins/blink.lua @@ -0,0 +1,14 @@ +-- The tmux prefix is C-Space, and tmux intercepts the prefix key before the +-- focused app (Neovim) ever sees it. blink.cmp's default binding +-- (toggle completion) would therefore be a dead key inside tmux. Free it so it +-- isn't a phantom binding; LazyVim's blink shows the completion menu +-- automatically as you type, so no manual trigger is needed. If you ever want a +-- manual trigger reachable inside tmux, bind one to a non-prefix key here. +return { + "saghen/blink.cmp", + opts = { + keymap = { + [""] = {}, + }, + }, +} diff --git a/home/dot_config/nvim/lua/plugins/editor.lua b/home/dot_config/nvim/lua/plugins/editor.lua new file mode 100644 index 0000000..dd58978 --- /dev/null +++ b/home/dot_config/nvim/lua/plugins/editor.lua @@ -0,0 +1,29 @@ +return { + -- Seamless C-h/j/k/l navigation across Neovim splits AND tmux panes. + -- The tmux half lives in ~/.config/tmux/tmux.conf (inline bind-key lines). + -- Safe to lazy-load: the tmux side does the "is this pane running vim?" check, + -- so Neovim only needs to register these commands/keys. + { + "christoomey/vim-tmux-navigator", + cmd = { + "TmuxNavigateLeft", + "TmuxNavigateDown", + "TmuxNavigateUp", + "TmuxNavigateRight", + "TmuxNavigatePrevious", + "TmuxNavigatorProcessList", + }, + keys = { + { "", "TmuxNavigateLeft" }, + { "", "TmuxNavigateDown" }, + { "", "TmuxNavigateUp" }, + { "", "TmuxNavigateRight" }, + { "", "TmuxNavigatePrevious" }, + }, + }, + + -- Keeps a live Session.vim in the cwd so tmux-resurrect's + -- @resurrect-strategy-nvim 'session' can restore your Neovim session + -- (open buffers/layout) after a reboot. No configuration needed. + { "tpope/vim-obsession" }, +} From adf47cfc8690274eedf8bc1298e4041997a602a9 Mon Sep 17 00:00:00 2001 From: Jarod Taylor Date: Mon, 8 Jun 2026 16:48:13 -0500 Subject: [PATCH 4/5] fix(tmux): POSIX-portable vim_pattern; correct plugin comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR #7 review (Copilot + Claude): - vim_pattern used \S (GNU/PCRE), which breaks under macOS/BSD grep -E where `ps -o comm=` returns nvim's full path — silently degrading C-hjkl to tmux-only nav. Use [^[:space:]] (POSIX non-whitespace), correct on both BSD and GNU (the suggested [^ \t] would wrongly exclude paths containing 't'). - Fix the backwards catppuccin path comment and the inaccurate "prepends". The 'C-\' quoting concern was a false positive (single quotes are literal). --- home/.chezmoiexternal.toml | 6 +++--- home/dot_config/tmux/tmux.conf | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/home/.chezmoiexternal.toml b/home/.chezmoiexternal.toml index d507e66..cf1c7e8 100644 --- a/home/.chezmoiexternal.toml +++ b/home/.chezmoiexternal.toml @@ -10,9 +10,9 @@ refreshPeriod = "168h" # tmux side is a handful of stable bind-key lines written inline in tmux.conf. # catppuccin/tmux — PINNED to v2.3.0 (it shipped breaking v2 syntax, so a -# moving target would break the status line). The key matches catppuccin's -# upstream clone path (repo root holds catppuccin.tmux under tmux/), so -# `run ~/.config/tmux/plugins/catppuccin/tmux/catppuccin.tmux` resolves. +# moving target would break the status line). The key below IS the clone +# destination dir; catppuccin.tmux sits at the repo root, so the full path +# `~/.config/tmux/plugins/catppuccin/tmux/catppuccin.tmux` resolves. # Pinned entries omit refreshPeriod — the shallow tag clone is frozen. [".config/tmux/plugins/catppuccin/tmux"] type = "git-repo" diff --git a/home/dot_config/tmux/tmux.conf b/home/dot_config/tmux/tmux.conf index 419a98d..5376942 100644 --- a/home/dot_config/tmux/tmux.conf +++ b/home/dot_config/tmux/tmux.conf @@ -47,7 +47,9 @@ bind -T copy-mode-vi y send-keys -X copy-selection-and-cancel # prefix. The is_vim check asks "is the focused pane running Neovim?" — if so the # keys pass through to Neovim; otherwise tmux moves the pane. (Neovim side is the # christoomey/vim-tmux-navigator plugin spec.) -vim_pattern='(\S+/)?g?\.?(view|l?n?vim?x?|fzf)(diff)?(-wrapped)?' +# [^[:space:]] (POSIX non-whitespace), not \S — \S is a GNU/PCRE extension that +# breaks under macOS/BSD grep -E, where `ps -o comm=` returns nvim's full path. +vim_pattern='([^[:space:]]+/)?g?\.?(view|l?n?vim?x?|fzf)(diff)?(-wrapped)?' is_vim="ps -o state= -o comm= -t '#{pane_tty}' \ | grep -iqE '^[^TXZ ]+ +${vim_pattern}$'" bind -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L' @@ -66,7 +68,7 @@ bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reload # ═══ Plugins ═════════════════════════════════════════════════════════════════ # Vendored to ~/.config/tmux/plugins/ by .chezmoiexternal.toml. The LOAD ORDER -# below is load-bearing: continuum PREPENDS its autosave hook to status-right, +# below is load-bearing: continuum extends status-right with its autosave hook, # and catppuccin SETS status-right — so catppuccin must load before continuum or # the autosave is silently lost. Order: menus → catppuccin → resurrect → continuum. From aa9aafd8cdf540db57946bd43aae2b7b953564ea Mon Sep 17 00:00:00 2001 From: Jarod Taylor Date: Mon, 8 Jun 2026 17:00:42 -0500 Subject: [PATCH 5/5] style(nvim): disable blink with false, not empty table Addresses PR #7 review. `{}` already disables the preset mapping (blink docs list `false` and `{}` as equivalent), but `false` is the more explicit intent. --- home/dot_config/nvim/lua/plugins/blink.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/home/dot_config/nvim/lua/plugins/blink.lua b/home/dot_config/nvim/lua/plugins/blink.lua index 8d5b9cd..9eaea4b 100644 --- a/home/dot_config/nvim/lua/plugins/blink.lua +++ b/home/dot_config/nvim/lua/plugins/blink.lua @@ -8,7 +8,7 @@ return { "saghen/blink.cmp", opts = { keymap = { - [""] = {}, + [""] = false, }, }, }