Commit 079fba12ed49

Vincent Demeester <vincent@sbr.pm>
2026-02-24 06:23:24
refactor(zsh): modular config in dots with perf optimizations
Moved ZSH runtime config from Nix initContent to modular dots/config/zsh/ structure inspired by smallwat3r's approach. Nix now only manages packages, env vars, and plugin fetching. Key changes: - Deferred compinit to precmd hook (saved ~53ms) - Replaced zsh-syntax-highlighting with ~60 line custom impl - Eliminated 4 subshell forks from prompt (precmd + vars) - Cached fzf/direnv init output to avoid fork on startup - Deferred lazyworktree discovery to first prompt (~210ms) - Added bytecode compilation, rationalise-dots, $ alias - Disabled all enableZshIntegration flags in home-manager Result: zshrc init 125ms → 30ms, wall-clock 500ms → 175ms.
1 parent 843426d
dots/config/zsh/core/05-helpers.zsh
@@ -0,0 +1,29 @@
+# Shared helpers — loaded first, used everywhere
+
+# Bytecode compilation: compile .zsh → .zwc if source is newer
+_zsh_compile_if_needed() {
+  local src=$1 dst="${1}.zwc"
+  [[ -n $src && -r $src ]] || return 1
+  if [[ ! -f $dst || $src -nt $dst ]]; then
+    zcompile "$src" 2>/dev/null
+  fi
+}
+
+# Check if commands exist (hash lookup, no fork)
+has() {
+  local cmd
+  for cmd in "$@"; do
+    (( $+commands[$cmd] )) || return 1
+  done
+}
+
+# Source files from a directory, with bytecode compilation
+load_zsh_dir() {
+  local dir=$1 file
+  [[ -d $dir && -r $dir ]] || return 0
+  for file in "$dir"/*.zsh(N); do
+    [[ -r $file ]] || continue
+    _zsh_compile_if_needed "$file"
+    source "$file"
+  done
+}
dots/config/zsh/core/10-options.zsh
@@ -0,0 +1,28 @@
+# Shell options and history
+
+setopt HIST_FCNTL_LOCK
+setopt EXTENDED_HISTORY
+setopt HIST_EXPIRE_DUPS_FIRST
+setopt HIST_IGNORE_DUPS
+setopt HIST_IGNORE_SPACE
+setopt SHARE_HISTORY
+setopt autocd
+
+autoload -Uz select-word-style; select-word-style bash
+
+# Dumb terminal fallback (Emacs TRAMP, etc.)
+if [[ "$TERM" == "dumb" || "$TERM" == "emacs" ]]; then
+  TERM=eterm-color
+  unsetopt zle
+  unsetopt prompt_cr
+  unsetopt prompt_subst
+  unfunction precmd 2>/dev/null
+  unfunction preexec 2>/dev/null
+  PS1='$ '
+  return
+fi
+
+# Profiling support: DEBUG_ZSH_PERF=1 zsh -i
+if (( ${+DEBUG_ZSH_PERF} )); then
+  zmodload zsh/zprof
+fi
dots/config/zsh/core/20-completion.zsh
@@ -0,0 +1,109 @@
+# Deferred completion system — runs on first prompt, not at startup
+# Saves ~53ms by deferring compinit to precmd
+
+autoload -Uz compinit
+
+__deferred_compinit() {
+  local dump=${XDG_CACHE_HOME:-$HOME/.cache}/zsh/.zcompdump
+  mkdir -p "${dump:h}" 2>/dev/null
+
+  if [[ ! -f $dump ]]; then
+    compinit -d "$dump"
+  else
+    compinit -C -d "$dump"  # -C skips security check (single-user machine)
+  fi
+  _zsh_compile_if_needed "$dump"
+
+  # Wire up alias expansion (needs compinit)
+  zle -C alias-expansion complete-word _generic
+  bindkey '^a' alias-expansion
+  zstyle ':completion:alias-expansion:*' completer _expand_alias
+
+  add-zsh-hook -d precmd __deferred_compinit
+  unfunction __deferred_compinit
+}
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd __deferred_compinit
+
+# Completion styles (zstyle declarations are cheap — no need to defer)
+zstyle ':completion:*' menu select
+zstyle ':completion::complete:*' use-cache on
+zstyle ':completion::complete:*' cache-path "${XDG_CACHE_HOME:-$HOME/.cache}/zsh/zcompcache"
+
+# Case-insensitive, partial-word, substring completion
+zstyle ':completion:*' matcher-list '' \
+  'm:{a-z\-}={A-Z\_}' \
+  'r:[^[:alpha:]]||[[:alpha:]]=** r:|=* m:{a-z\-}={A-Z\_}' \
+  'r:[[:ascii:]]||[[:ascii:]]=** r:|=* m:{a-z\-}={A-Z\_}'
+
+# Group matches and describe
+zstyle ':completion:*:*:*:*:*' menu select
+zstyle ':completion:*:matches' group 'yes'
+zstyle ':completion:*:options' description 'yes'
+zstyle ':completion:*:options' auto-description '%d'
+zstyle ':completion:*:corrections' format ' %F{green}-- %d (errors: %e) --%f'
+zstyle ':completion:*:descriptions' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*:messages' format ' %F{purple} -- %d --%f'
+zstyle ':completion:*:warnings' format ' %F{red}-- no matches found --%f'
+zstyle ':completion:*:default' list-prompt '%S%M matches%s'
+zstyle ':completion:*' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*' verbose yes
+
+# Fuzzy match mistyped completions
+zstyle ':completion:*' completer _complete _list _match _approximate
+zstyle ':completion:*:match:*' original only
+zstyle ':completion:*:approximate:*' max-errors 1 numeric
+zstyle -e ':completion:*:approximate:*' max-errors 'reply=($((($#PREFIX+$#SUFFIX)/3))numeric)'
+
+# Don't complete unavailable commands
+zstyle ':completion:*:functions' ignored-patterns '(_*|pre(cmd|exec))'
+
+# Array completion element sorting
+zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters
+
+# Directories
+zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
+zstyle ':completion:*:*:cd:*' ignore-parents parent pwd
+zstyle ':completion:*:*:cd:*' tag-order local-directories directory-stack path-directories
+zstyle ':completion:*:*:cd:*:directory-stack' menu yes select
+zstyle ':completion:*:-tilde-:*' group-order 'named-directories' 'path-directories' 'users' 'expand'
+zstyle ':completion:*' squeeze-slashes true
+zstyle ':completion:*' special-dirs true
+
+# Environmental variables
+zstyle ':completion::*:(-command-|export):*' fake-parameters ${${${_comps[(I)-value-*]#*,}%%,*}:#-*-}
+
+# Populate hostname completion
+zstyle -e ':completion:*:hosts' hosts 'reply=(
+  ${=${=${=${${(f)"$(cat {/etc/ssh_,~/.ssh/known_}hosts(|2)(N) 2>/dev/null)"}%%[#| ]*}//\]:[0-9]*/ }//,/ }//\[/ }
+  ${=${${${${(@M)${(f)"$(cat ~/.ssh/config 2>/dev/null)"}:#Host *}#Host }:#*\**}:#*\?*}}
+)'
+
+# Don't complete uninteresting users
+zstyle ':completion:*:*:*:users' ignored-patterns \
+  adm amanda apache avahi beaglidx bin cacti canna clamav daemon \
+  dbus distcache dovecot fax ftp games gdm gkrellmd gopher \
+  hacluster haldaemon halt hsqldb ident junkbust ldap lp mail \
+  mailman mailnull mldonkey mysql nagios \
+  named netdump news nfsnobody nobody nscd ntp nut nx openvpn \
+  operator pcap postfix postgres privoxy pulse pvm quagga radvd \
+  rpc rpcuser rpm shutdown squid sshd sync uucp vcsa xfs '_*'
+zstyle '*' single-ignored show
+
+# Ignore multiple entries
+zstyle ':completion:*:(rm|kill|diff):*' ignore-line other
+zstyle ':completion:*:rm:*' file-patterns '*:all-files'
+
+# Man
+zstyle ':completion:*:manuals' separate-sections true
+zstyle ':completion:*:manuals.(^1*)' insert-sections true
+
+# SSH/SCP/RSYNC
+zstyle ':completion:*:(scp|rsync):*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
+zstyle ':completion:*:(scp|rsync):*' group-order users files all-files hosts-domain hosts-host hosts-ipaddr
+zstyle ':completion:*:ssh:*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
+zstyle ':completion:*:ssh:*' group-order users hosts-domain hosts-host users hosts-ipaddr
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-host' ignored-patterns '*(.|:)*' loopback ip6-loopback localhost ip6-localhost broadcasthost
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-domain' ignored-patterns '<->.<->.<->.<->' '^[-[:alnum:]]##(.[-[:alnum:]]##)##' '*@*'
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-ipaddr' ignored-patterns '^(<->.<->.<->.<->|(|::)([[:xdigit:].]##:(#c,2))##(|%*))' '127.0.0.<->' '255.255.255.255' '::1' 'fe80::*'
dots/config/zsh/core/30-prompt.zsh
@@ -0,0 +1,65 @@
+# Prompt — computed in precmd, no subshell forks
+# Variables are set in the hook, PROMPT just references them
+
+__prompt_precmd() {
+  local last_status=$?
+
+  __ps_err='' __ps_dir='' __ps_git='' __ps_nix='' __ps_kube=''
+
+  # Exit status
+  (( last_status )) && __ps_err="%F{red}?${last_status} "
+
+  # Directory with smart shortening (inline, no fork)
+  local dir="${PWD/#$HOME/~}"
+  local prefix=""
+  [[ "$dir" == */.local/share/worktrees/* ]] && prefix="🌿 "
+  [[ "$dir" == ~/desktop* ]] && prefix="🖥️ "
+
+  if (( ${#dir} > 50 )); then
+    local parts=("${(@s:/:)dir}")
+    local i
+    for (( i=1; i <= ${#parts[@]} - 2; i++ )); do
+      (( ${#dir} <= 50 )) && break
+      (( ${#parts[$i]} <= 2 )) && continue
+      if [[ "${parts[$i]}" == .* ]]; then
+        parts[$i]=".${parts[$i]:1:1}"
+      else
+        parts[$i]="${parts[$i]:0:1}"
+      fi
+      dir="${(j:/:)parts}"
+    done
+  fi
+  __ps_dir="${prefix}${dir}"
+
+  # Git: single call for branch + dirty state
+  local gstatus branch dirty
+  if gstatus=$(GIT_OPTIONAL_LOCKS=0 git status --porcelain=v2 -b --no-ahead-behind 2>/dev/null); then
+    branch=${gstatus#*branch.head }
+    branch=${branch%%$'\n'*}
+    if [[ -n $branch && $branch != "# "* ]]; then
+      [[ $gstatus == *$'\n'[^#]* ]] && dirty='*'
+      __ps_git=" %F{76}${branch}${dirty}%f"
+    fi
+  fi
+
+  # Nix shell indicator
+  if (( ${+IN_NIX_SHELL} )); then
+    if [[ -n $NIX_SHELL_PACKAGES ]]; then
+      __ps_nix=" %F{yellow}❄{$NIX_SHELL_PACKAGES}%f"
+    else
+      __ps_nix=" %F{yellow}❄%f"
+    fi
+  fi
+
+  # Kubeconfig indicator
+  if [[ -n $KUBECONFIG ]]; then
+    __ps_kube=" %F{134}k8s:${KUBECONFIG:t:r}%f"
+  fi
+}
+
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd __prompt_precmd
+
+setopt PROMPT_SUBST
+PROMPT='${__ps_err}%F{111}%m%f:%F{2}${__ps_dir}%f${__ps_git}${__ps_nix}${__ps_kube} %(!.#.$) '
+RPROMPT=""
dots/config/zsh/core/40-keybindings.zsh
@@ -0,0 +1,13 @@
+# Key bindings
+
+# Edit command line in $EDITOR
+autoload -U edit-command-line
+zle -N edit-command-line
+bindkey '^e' edit-command-line
+
+# Rationalise dots: ... → ../..  .... → ../../..
+__rationalise-dot() {
+  [[ $LBUFFER = *.. ]] && LBUFFER+=/.. || LBUFFER+=.
+}
+zle -N __rationalise-dot
+bindkey "." __rationalise-dot
dots/config/zsh/core/45-auto-expand.zsh
@@ -0,0 +1,102 @@
+# Auto-expanding aliases — expands abbreviations on space
+typeset -ga _vbe_abbrevations
+abbrev-alias() {
+  alias $1
+  _vbe_abbrevations+=(${1%%\=*})
+}
+_vbe_zle-autoexpand() {
+  local -a words; words=(${(z)LBUFFER})
+  if (( ${#_vbe_abbrevations[(r)${words[-1]}]} )); then
+    zle _expand_alias
+  fi
+  zle magic-space
+}
+zle -N _vbe_zle-autoexpand
+bindkey -M emacs " " _vbe_zle-autoexpand
+bindkey -M emacs "^ " magic-space
+bindkey -M isearch " " magic-space
+
+# Correct common typos
+has git  && abbrev-alias gti=git
+has grep && abbrev-alias grpe=grep
+has sudo && abbrev-alias suod=sudo
+has ssh  && abbrev-alias shs=ssh
+
+# Save keystrokes
+has git && abbrev-alias gls="git ls-files"
+has ip && {
+  abbrev-alias ip6='ip -6'
+  abbrev-alias ipb='ip -brief'
+}
+abbrev-alias tailf="tail -F"
+has mpv && abbrev-alias mpva="mpv --no-video"
+
+# Systemd aliases
+() {
+  local cmd
+  local -a cmds
+  cmds=(start stop reload restart status)
+
+  if [[ -d /run/systemd/system ]]; then
+    for cmd ($cmds) {
+      abbrev-alias $cmd="${(%):-%(#..sudo )}systemctl $cmd"
+      abbrev-alias u$cmd="systemctl --user $cmd"
+    }
+  else
+    for cmd ($cmds) {
+      function $cmd() {
+        name=$1 ; shift
+        ${(%):-%(#..sudo)} service $name $0 "$@"
+      }
+      (( $+functions[compdef] )) && compdef _services $cmd
+    }
+  fi
+}
+
+# grep aliases
+() {
+  local grep=grep
+  (( $+commands[ggrep] )) && grep=ggrep
+  local colors="--color=auto"
+  $grep -q $colors . <<< yes 2>/dev/null || colors=""
+  alias grep="command ${grep} ${colors}"
+  abbrev-alias rgrep="grep -r"
+  abbrev-alias egrep="grep -E"
+  abbrev-alias fgrep="grep -F"
+  (( $+commands[zgrep] )) && alias zgrep="GREP=${grep} command zgrep ${colors}"
+}
+
+# nixpkgs runner
+(( $+commands[nix] )) && nixpkgs() {
+  cmd=$1; shift
+  nix run nixpkgs\#${cmd} -- "$@"
+}
+
+# File viewer
+v() {
+  case $(file --brief --mime-type $1 2>/dev/null) in
+    image/svg+xml) ;;
+    image/*) (( $+commands[nsxiv] )) && ${I3SOCK+i3-tabbed} nsxiv $1; return ;;
+    video/*) (( $+commands[mpv] )) && ${I3SOCK+i3-tabbed} mpv --no-fs $1; return ;;
+  esac
+  if (( $+commands[bat] )); then
+    if (( ! $# )); then
+      gzip -cdfq | bat
+    else
+      for f in "$@"; do gzip -cdfq -- $f | bat --file-name ${f%.gz}; done
+    fi
+  elif (( $+commands[less] )); then
+    gzip -cdfq -- "$@" | less -FX
+  elif (( $+commands[zmore] )); then
+    zmore "$@"
+  elif (( $+commands[more] )); then
+    gzip -cdfq -- "$@" | more
+  else
+    gzip -cdfq -- "$@"
+  fi
+}
+
+# jayrah
+if [[ -d ${HOME}/src/github.com/chmouel/jayrah ]]; then
+  alias jayrah="uv --directory=${HOME}/src/github.com/chmouel/jayrah run jayrah"
+fi
dots/config/zsh/core/50-aliases.zsh
@@ -0,0 +1,45 @@
+# Aliases
+
+# Paste commands from docs: $ git status → just works
+alias \$=" "
+alias %=" "
+
+# Safe file operations
+alias mkdir="mkdir --parents --verbose"
+alias rm="rm --interactive"
+alias cp="cp --interactive"
+alias mv="mv --interactive"
+
+# ls → eza (base aliases come from home-manager eza module)
+has eza && {
+  alias l="ls -lah"
+  alias lt="eza --tree"
+}
+
+# Common shortcuts
+alias map="xargs -n1"
+alias gcd='cd $(git rev-parse --show-toplevel)'
+has nvim && alias vim=nvim
+alias wget="wget -c --hsts-file=${XDG_DATA_HOME:-$HOME/.local/share}/wget-hsts"
+has kubectl && alias k=kubectl
+
+# Finance aliases come from home/common/desktop/finance.nix
+
+# Global aliases (expand anywhere in a command)
+alias -g L="|less"
+alias -g EEL=' 2>&1 | less'
+alias -g GB='`git rev-parse --abbrev-ref HEAD`'
+alias -g GR='`git rev-parse --show-toplevel`'
+(( $+commands[jq] )) && alias -g MJ="| jq -C '.'" || alias -g MJ="| python -mjson.tool"
+
+# Suffix aliases (open by extension)
+alias -s {ape,avi,flv,m4a,mkv,mov,mp3,mp4,mpeg,mpg,ogg,ogm,wav,webm}=mpv
+alias -s org=emacs
+
+# Emacs integration
+[[ -n $INSIDE_EMACS ]] && \
+function ff () { print "\e]51;Efind-file $(readlink -f $1)\e\\"; }
+
+# Eat shell integration
+[ -n "$EAT_SHELL_INTEGRATION_DIR" ] && \
+  source "$EAT_SHELL_INTEGRATION_DIR/zsh"
dots/config/zsh/core/60-syntax-hl.zsh
@@ -0,0 +1,71 @@
+# Minimal syntax highlighting using ZSH's built-in region_highlight
+# Replaces zsh-syntax-highlighting plugin (~60 lines vs ~4000 lines)
+# Highlights: unknown commands (red), rm (dim bold), sudo (purple), quoted strings (yellow)
+
+__syntax_hl() {
+  emulate -L zsh
+
+  # Skip if buffer unchanged (redraws without edits)
+  [[ $BUFFER == "${__syntax_hl_prev-}" ]] && return
+  __syntax_hl_prev=$BUFFER
+
+  region_highlight=()
+  (( $#BUFFER )) || return
+
+  # First word (skip leading whitespace and VAR=val prefixes)
+  local cmd=${BUFFER##[[:space:]]#}
+  local -i offset=$(( $#BUFFER - $#cmd ))
+  cmd=${cmd%%[[:space:]]*}
+  [[ -n $cmd ]] || return
+
+  while [[ $cmd == *=* ]]; do
+    local rest=${BUFFER:$(( offset + $#cmd ))}
+    rest=${rest##[[:space:]]#}
+    offset=$(( $#BUFFER - $#rest ))
+    cmd=${rest%%[[:space:]]*}
+    [[ -n $cmd ]] || return
+  done
+
+  local -i cmd_end=$(( offset + $#cmd ))
+
+  # rm: dim bold on the whole line (visual warning)
+  if [[ $cmd == rm ]]; then
+    region_highlight+=("0 $#BUFFER fg=90,bold")
+    return
+  fi
+
+  # sudo: purple bold
+  if [[ $cmd == sudo ]]; then
+    region_highlight+=("$offset $cmd_end fg=164,bold")
+    return
+  fi
+
+  # unknown command: red bold
+  if ! whence -- "$cmd" >/dev/null 2>&1; then
+    region_highlight+=("$offset $cmd_end fg=red,bold")
+  fi
+
+  # Quoted strings: yellow
+  local QS="'" QD='"'
+  local -i pos=1 sq dq next close
+  while (( pos <= $#BUFFER )); do
+    sq=${BUFFER[(ib:pos:)$QS]}
+    dq=${BUFFER[(ib:pos:)$QD]}
+    if (( sq < dq )); then
+      next=$sq
+      close=${BUFFER[(ib:next+1:)$QS]}
+    elif (( dq <= $#BUFFER )); then
+      next=$dq
+      close=${BUFFER[(ib:next+1:)$QD]}
+    else
+      break
+    fi
+    if (( close <= $#BUFFER )); then
+      region_highlight+=("$(( next - 1 )) $close fg=yellow")
+      pos=$(( close + 1 ))
+    else
+      break
+    fi
+  done
+}
+zle -N zle-line-pre-redraw __syntax_hl
dots/config/zsh/functions/j
@@ -0,0 +1,30 @@
+local root=~/src/
+local res results args
+
+while getopts "np" opt; do
+  if [[ $opt = "?" ]]; then
+    print -r -- "$myname: unrecognized option: -$OPTARG" >&2
+    return 1
+  fi
+  eval "opt_$opt=\${OPTARG:--\$opt}"
+done
+(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
+
+local fnd=$1
+type -f zshz 2>/dev/null >/dev/null || opt_n=true
+
+if [[ -n ${fnd} ]];then
+
+    [[ -z ${opt_n} ]] && {
+        local zz=$(zshz -e ${fnd})
+        [[ -n ${zz} ]] && { echo "ZSHZ: ${zz}"; [[ -z ${opt_p} ]] && cd ${zz}; return;}
+    }
+
+    local results=($(fd -d 3 -t d . ${root}|egrep -i "${fnd}"))
+    [[ ${#results} == 1 ]] && { echo ${results}; [[ -z ${opt_p} ]] && cd ${results} ; return;}
+    [[ ${#results} == 0 ]] && { echo "No results found for ${fnd}"; return 1;}
+    args="-q ${fnd}"
+fi
+
+res=$(fd -d 3 -t d . ${root}|sed "s,${root},,"|fzf --height 50% --border ${args})
+[[ -n ${res} ]] && {echo ${root}${res}; [[ -z ${opt_p} ]]  && cd ${root}${res} ; }
dots/config/zsh/functions/timezsh
@@ -0,0 +1,12 @@
+zmodload zsh/datetime
+runs=${1:-10}
+total_ms=0
+for i in {1..$runs}; do
+  start=$EPOCHREALTIME
+  zsh -i -c exit >/dev/null 2>&1
+  end=$EPOCHREALTIME
+  delta_ms=$(( (end - start) * 1000 ))
+  total_ms=$(( total_ms + delta_ms ))
+  printf "run %2d: %3.0f ms\n" "$i" "$delta_ms"
+done
+printf "\nAverage: %.0f ms\n" "$(( total_ms / runs ))"
dots/config/zsh/tools/autosuggestions.zsh
@@ -0,0 +1,12 @@
+# Zsh autosuggestions
+local plugin
+for plugin in \
+  /etc/profiles/per-user/$USER/share/zsh-autosuggestions/zsh-autosuggestions.zsh \
+  /run/current-system/sw/share/zsh-autosuggestions/zsh-autosuggestions.zsh \
+  /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh; do
+  if [[ -f "$plugin" ]]; then
+    source "$plugin"
+    ZSH_AUTOSUGGEST_STRATEGY=(history)
+    break
+  fi
+done
dots/config/zsh/tools/broot.zsh
@@ -0,0 +1,12 @@
+# Broot shell integration
+has broot || return
+
+local broot_init
+for broot_init in \
+  "${XDG_CONFIG_HOME:-$HOME/.config}/broot/launcher/bash/br" \
+  "$HOME/.config/broot/launcher/bash/br"; do
+  if [[ -f "$broot_init" ]]; then
+    source "$broot_init"
+    break
+  fi
+done
dots/config/zsh/tools/direnv.zsh
@@ -0,0 +1,12 @@
+# Direnv — cached hook to avoid fork on every startup
+has direnv || return
+
+local cache=${XDG_CACHE_HOME:-$HOME/.cache}/zsh/direnv-init.zsh
+local bin=${commands[direnv]}
+
+if [[ ! -f $cache || $bin -nt $cache ]]; then
+  mkdir -p ${cache:h}
+  direnv hook zsh > "$cache"
+  _zsh_compile_if_needed "$cache"
+fi
+source "$cache"
dots/config/zsh/tools/fzf.zsh
@@ -0,0 +1,13 @@
+# FZF — cached init to avoid fork on every startup
+has fzf || return
+[[ $options[zle] = on ]] || return
+
+local cache=${XDG_CACHE_HOME:-$HOME/.cache}/zsh/fzf-init.zsh
+local bin=${commands[fzf]}
+
+if [[ ! -f $cache || $bin -nt $cache ]]; then
+  mkdir -p ${cache:h}
+  fzf --zsh > "$cache"
+  _zsh_compile_if_needed "$cache"
+fi
+source "$cache"
dots/config/zsh/tools/kitty.zsh
@@ -0,0 +1,17 @@
+# Kitty terminal integration
+[[ -n "$KITTY_INSTALLATION_DIR" ]] || return
+
+export KITTY_SHELL_INTEGRATION="no-rc"
+autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration
+kitty-integration
+unfunction kitty-integration
+
+# SSH wrapper: use raw ssh for shpool sessions (host/session pattern)
+# Kitty SSH kitten interferes with RemoteCommand
+ssh() {
+  if [[ "$1" =~ / ]]; then
+    command ssh "$@"
+  else
+    kitty +kitten ssh "$@"
+  fi
+}
dots/config/zsh/tools/kubectl.zsh
@@ -0,0 +1,6 @@
+# Kubectl config switcher
+has kubectl || return
+
+local plugin_dir="${ZDOTDIR:-$HOME/.config/zsh}/plugins/kubectl-config-switcher"
+[[ -f "$plugin_dir/kubectl-config-switcher.plugin.zsh" ]] && \
+  source "$plugin_dir/kubectl-config-switcher.plugin.zsh"
dots/config/zsh/tools/lazyworktree.zsh
@@ -0,0 +1,86 @@
+# Lazyworktree integration
+has lazyworktree || return
+
+# Source shell functions (fast: ~0.4ms)
+local lw_functions="${commands[lazyworktree]:h:h}/share/lazyworktree/functions.zsh"
+[[ -f "$lw_functions" ]] && source "$lw_functions"
+
+# Named repo shortcuts
+wh()   { worktree_jump "$HOME/src/home" "$@"; }
+wnix() { worktree_jump "$HOME/src/nixpkgs" "$@"; }
+_wh()   { _worktree_jump "$HOME/src/home"; }
+_wnix() { _worktree_jump "$HOME/src/nixpkgs"; }
+(( $+functions[compdef] )) && { compdef _wh wh; compdef _wnix wnix; }
+
+# Auto-discover worktree aliases from source directories
+_lazyworktree_discover_aliases() {
+  setopt local_options nullglob
+  local -A repo_dirs=()
+  local base_dirs=(
+    "$HOME/src/gitlab.com:wgb"
+    "$HOME/src/github.com:wgh"
+    "$HOME/src/osp:wo"
+    "$HOME/src:ws"
+    "$HOME/src/tektoncd:wt"
+  )
+  local entry base_dir prefix repo_dir repo_name target_dir alias_name org_name
+
+  for entry in "${base_dirs[@]}"; do
+    base_dir="${entry%%:*}"
+    prefix="${entry##*:}"
+    [[ -d "$base_dir" ]] || continue
+
+    if [[ "$prefix" == "wgh" ]] || [[ "$prefix" == "wgb" ]]; then
+      eval "${prefix}_() {
+        local selection org_dir
+        selection=\$(find \"$base_dir\" -maxdepth 2 -name .git -type d 2>/dev/null | sed 's|/.git$||' | sed \"s|^$base_dir/||\" | fzf --prompt=\"$base_dir: \")
+        [[ -n \"\$selection\" ]] || return 1
+        worktree_jump \"$base_dir/\$selection\" \"\$@\"
+      }"
+      for org_dir in "$base_dir"/*/; do
+        [[ -d "$org_dir" ]] || continue
+        org_name="$(basename "$org_dir")"
+        for repo_dir in "$org_dir"/*/; do
+          [[ -d "$repo_dir" ]] || continue
+          [[ -d "$repo_dir/.git" ]] || continue
+          repo_name="$(basename "$repo_dir")"
+          target_dir="$repo_dir"
+          alias_name="${prefix}_${org_name}_${repo_name}"
+          repo_dirs[$alias_name]="$target_dir"
+          eval "${alias_name}() { worktree_jump \"$target_dir\" \"\$@\"; }"
+        done
+      done
+    else
+      eval "${prefix}_() {
+        local selection
+        selection=\$(ls -1 \"$base_dir\" 2>/dev/null | fzf --prompt=\"$base_dir: \")
+        [[ -n \"\$selection\" ]] || return 1
+        if [[ -d \"$base_dir/\$selection/.git\" ]]; then
+          worktree_jump \"$base_dir/\$selection\" \"\$@\"
+        else
+          echo \"Not a git repo: $base_dir/\$selection\" >&2
+          return 1
+        fi
+      }"
+      for repo_dir in "$base_dir"/*/; do
+        [[ -d "$repo_dir" ]] || continue
+        repo_name="$(basename "$repo_dir")"
+        [[ -d "$repo_dir/.git" ]] || continue
+        target_dir="$repo_dir"
+        alias_name="${prefix}_${repo_name}"
+        if [[ -n "${repo_dirs[$alias_name]}" ]] && [[ "${repo_dirs[$alias_name]}" != "$target_dir" ]]; then
+          alias_name="${prefix}_${repo_name}"
+        fi
+        repo_dirs[$alias_name]="$target_dir"
+        eval "${alias_name}() { worktree_jump \"$target_dir\" \"\$@\"; }"
+      done
+    fi
+  done
+}
+# Defer discovery to first prompt (saves ~210ms)
+__deferred_lazyworktree() {
+  _lazyworktree_discover_aliases >/dev/null 2>&1
+  add-zsh-hook -d precmd __deferred_lazyworktree
+  unfunction __deferred_lazyworktree
+}
+add-zsh-hook precmd __deferred_lazyworktree
dots/config/zsh/tools/nix-shell.zsh
@@ -0,0 +1,3 @@
+# Nix shell plugin
+local plugin_dir="${ZDOTDIR:-$HOME/.config/zsh}/plugins/zsh-nix-shell"
+[[ -f "$plugin_dir/nix-shell.plugin.zsh" ]] && source "$plugin_dir/nix-shell.plugin.zsh"
dots/config/zsh/tools/walk.zsh
@@ -0,0 +1,8 @@
+# Walk — terminal navigator
+has walk || return
+
+export WALK_REMOVE_CMD=trash-put
+export WALK_OPEN_WITH="nix:vim;go:vim;rs:vim;py:vim;sh:vim;bash:vim;zsh:vim;md:glow -p;txt:less -N"
+function lk {
+  cd "$(walk --icons "$@")"
+}
dots/config/zsh/tools/zsh-z.zsh
@@ -0,0 +1,6 @@
+# zsh-z (directory jumping)
+local plugin_dir="${ZDOTDIR:-$HOME/.config/zsh}/plugins/zsh-z"
+[[ -f "$plugin_dir/zsh-z.plugin.zsh" ]] || return
+source "$plugin_dir/zsh-z.plugin.zsh"
+export _Z_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/z"
+(( $+functions[zshz] )) && (( $+functions[compdef] )) && compdef _zshz j
dots/config/zsh/.gitignore
@@ -0,0 +1,1 @@
+*.zwc
dots/config/zsh/init.zsh
@@ -0,0 +1,27 @@
+# ZSH modular config entrypoint
+# Sourced from home-manager's .zshrc after its fpath/plugin setup
+#
+# Structure:
+#   core/   — numbered files loaded in order (helpers, options, completion, prompt, etc.)
+#   tools/  — per-tool configs (each guards with `has <tool> || return`)
+#   functions/ — autoloaded functions (zero startup cost)
+
+local zsh_dir="${ZDOTDIR:-$HOME/.config/zsh}"
+
+# Register autoloaded functions
+if [[ -d "$zsh_dir/functions" ]]; then
+  fpath=("$zsh_dir/functions" $fpath)
+  autoload -U "$zsh_dir"/functions/*(x:tN)
+fi
+
+# Load core config (numbered for ordering)
+source "$zsh_dir/core/05-helpers.zsh"
+load_zsh_dir "$zsh_dir/core"
+
+# Load tool configs
+load_zsh_dir "$zsh_dir/tools"
+
+# Profiling output (if enabled)
+if (( ${+DEBUG_ZSH_PERF} )); then
+  zprof
+fi
dots/Makefile
@@ -86,6 +86,11 @@ agent-skill-manager-bin : ~/bin/agent-skill-manager
 	@mkdir -p ~/bin
 	@ln -snf $< $@
 
+##@ Shell
+
+all += zsh
+zsh : ~/.config/zsh/init.zsh ~/.config/zsh/core ~/.config/zsh/tools ~/.config/zsh/functions
+
 ##@ Dev Tools
 
 all += git-template copilot-hooks opencode-plugin lazygit lazyworktree lazypr
home/common/desktop/kitty.nix
@@ -2,7 +2,7 @@
 {
   programs.kitty = {
     enable = true;
-    shellIntegration.enableZshIntegration = true;
+    shellIntegration.enableZshIntegration = false; # handled in dots/config/zsh/tools/kitty.zsh
     settings = {
       close_on_child_death = "yes";
       font_family = "JetBrains Mono";
@@ -396,16 +396,5 @@
     '';
   };
 
-  programs.zsh.initContent = ''
-    # SSH wrapper - use raw ssh for shpool sessions (host/session pattern)
-    # Kitty SSH kitten interferes with RemoteCommand
-    ssh() {
-      # Check if first argument contains / (shpool session pattern)
-      if [[ "$1" =~ / ]]; then
-        command ssh "$@"
-      else
-        kitty +kitten ssh "$@"
-      fi
-    }
-  '';
+  # SSH wrapper moved to dots/config/zsh/tools/kitty.zsh
 }
home/common/dev/lazyworktree.nix
@@ -5,7 +5,7 @@
   # Config is managed via dots/.config/lazyworktree/config.yaml
   programs.lazyworktree = {
     enable = true;
-    enableZshIntegration = true;
+    enableZshIntegration = false; # handled in dots/config/zsh/tools/lazyworktree.zsh
     package = pkgs.lazyworktree;
 
     # Explicit aliases for commonly used repositories
home/common/shell/atuin.nix
@@ -1,7 +1,7 @@
 _: {
   programs.atuin = {
     enable = true;
-    enableZshIntegration = true;
+    enableZshIntegration = false; # TODO: remove atuin entirely (see TODO)
     flags = [ "--disable-up-arrow" ];
     settings = {
       auto_sync = true;
home/common/shell/default.nix
@@ -15,7 +15,7 @@
   programs = {
     broot = {
       enable = true;
-      enableZshIntegration = true;
+      enableZshIntegration = false; # handled in dots/config/zsh/tools/broot.zsh
     };
     eza.enable = true;
     fd.enable = true;
home/common/shell/direnv.nix
@@ -3,7 +3,7 @@
 {
   programs.direnv = {
     enable = true;
-    enableZshIntegration = true;
+    enableZshIntegration = false; # handled in dots/config/zsh/tools/direnv.zsh
     stdlib = ''
       mkdir -p $HOME/.cache/direnv/layouts
       pwd_hash=$(echo -n $PWD | shasum | cut -d ' ' -f 1)
home/common/shell/fzf.nix
@@ -1,7 +1,7 @@
 _: {
   programs.fzf = {
     enable = true;
-    enableZshIntegration = true;
+    enableZshIntegration = false; # handled in dots/config/zsh/tools/fzf.zsh
     defaultOptions = [ "--bind=ctrl-j:accept" ];
     changeDirWidgetOptions = [ "--preview 'tree -C {} | head -200'" ];
     fileWidgetCommand = "rg --files";
home/common/shell/zsh.nix
@@ -1,28 +1,39 @@
 { config, pkgs, ... }:
 {
-  home.file."${config.programs.zsh.dotDir}/completion.zsh".source = ./zsh/completion.zsh;
-  home.file."${config.programs.zsh.dotDir}/prompt.zsh".source = ./zsh/prompt.zsh;
-  home.file."${config.programs.zsh.dotDir}/functions/j".source = ./zsh/j;
-  home.file."${config.programs.zsh.dotDir}/auto-expanding-aliases.zsh".source =
-    ./zsh/auto-expanding-aliases.zsh;
+  # Plugins: fetched by Nix, placed in ZDOTDIR/plugins, sourced by dots/config/zsh/tools/
+  home.file."${config.programs.zsh.dotDir}/plugins/kubectl-config-switcher".source =
+    pkgs.fetchFromGitHub {
+      owner = "chmouel";
+      repo = "kubectl-config-switcher";
+      rev = "5679aa70383cee93fc15351dd4895c29c90b78a5";
+      sha256 = "sha256-Aifa5ms2p/l0FkZE8Tep8QiDWUdfFfdKrTIbJNurxw4=";
+    };
+  home.file."${config.programs.zsh.dotDir}/plugins/zsh-z".source =
+    pkgs.fetchFromGitHub {
+      owner = "agkozak";
+      repo = "zsh-z";
+      rev = "aaafebcd97424c570ee247e2aeb3da30444299cd";
+      sha256 = "sha256-9Wr4uZLk2CvINJilg4o72x0NEAl043lP30D3YnHk+ZA=";
+    };
+  home.file."${config.programs.zsh.dotDir}/plugins/zsh-nix-shell".source =
+    pkgs.fetchFromGitHub {
+      owner = "chisui";
+      repo = "zsh-nix-shell";
+      rev = "v0.8.0";
+      sha256 = "sha256-Z6EYQdasvpl1P78poj9efnnLj7QQg13Me8x1Ryyw+dM=";
+    };
 
   home.packages = with pkgs; [
     nix-zsh-completions
+    zsh-autosuggestions
   ];
 
   programs.zsh = {
     enable = true;
-    # zprof.enable = true;
-    # See https://gist.github.com/ctechols/ca1035271ad134841284
-    completionInit = ''
-      autoload -Uz compinit
-      for dump in ${config.programs.zsh.dotDir}/.zcompdump(N.mh+24); do
-        compinit
-      done
-      compinit -C
-    '';
-    enableCompletion = true;
-    autosuggestion.enable = true;
+    # Completion is deferred in our own init.zsh (precmd hook)
+    completionInit = "";
+    enableCompletion = false;
+    autosuggestion.enable = false; # handled in dots/config/zsh/tools/autosuggestions.zsh
     autocd = true;
     dotDir = "${config.xdg.configHome}/zsh";
     defaultKeymap = "emacs";
@@ -41,130 +52,18 @@
       if [ -d $HOME/.krew/bin ]; then
         export PATH=$HOME/.krew/bin:$PATH
       fi
-      # TODO Move somewhere else
-      export TLDR_CACHE_DIR="$XDG_CACHE_HOME"/tldr 
+      export TLDR_CACHE_DIR="$XDG_CACHE_HOME"/tldr
     '';
-    # TODO Extract this to files.
+    # Thin wrapper — all runtime logic lives in dots/config/zsh/
     initContent = ''
-      # c.f. https://wiki.gnupg.org/AgentForwarding
-      # gpgconf --create-socketdir &!
-      path+="${config.programs.zsh.dotDir}/functions"
       fpath+="$HOME/.local/state/nix/profile/share/zsh/site-functions"
-      fpath+="${config.programs.zsh.dotDir}/functions"
-      for func (${config.programs.zsh.dotDir}/functions) autoload -U $func/*(x:t)
-      autoload -Uz select-word-style; select-word-style bash
-      #if [ -n "$INSIDE_EMACS" ]; then
-      #  chpwd() { print -P "\033AnSiTc %d" }
-      #  print -P "\033AnSiTu %n"
-      #  print -P "\033AnSiTc %d"
-      #fi
-      if [[ "$TERM" == "dumb" || "$TERM" == "emacs" ]]
-      then
-        TERM=eterm-color
-        unsetopt zle
-        unsetopt prompt_cr
-        unsetopt prompt_subst
-        unfunction precmd
-        unfunction preexec
-        PS1='$ '
-        return
-      fi
-      # eval "$(${config.programs.atuin.package}/bin/atuin init zsh)"
-      # make sure navigation using emacs keybindings works on all non-alphanumerics
-      # syntax highlighting
-      source ${config.programs.zsh.dotDir}/plugins/zsh-nix-shell/nix-shell.plugin.zsh
-      source ${pkgs.zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
-      ZSH_HIGHLIGHT_PATTERNS+=('rm -rf *' 'fg=white,bold,bg=red')
-      ZSH_HIGHLIGHT_PATTERNS+=('rm -fR *' 'fg=white,bold,bg=red')
-      ZSH_HIGHLIGHT_PATTERNS+=('rm -fr *' 'fg=white,bold,bg=red')
-      source ${config.programs.zsh.dotDir}/completion.zsh
-      source ${config.programs.zsh.dotDir}/prompt.zsh
-      source ${config.programs.zsh.dotDir}/plugins/kubectl-config-switcher/kubectl-config-switcher.plugin.zsh
-      source ${config.programs.zsh.dotDir}/auto-expanding-aliases.zsh
-      setopt HIST_IGNORE_SPACE
-      alias -g L="|less"
-      alias -g EEL=' 2>&1 | less'
-      alias -g GB='`git rev-parse --abbrev-ref HEAD`'
-      alias -g GR='`git rev-parse --show-toplevel`'
-      alias -s {ape,avi,flv,m4a,mkv,mov,mp3,mp4,mpeg,mpg,ogg,ogm,wav,webm}=mpv
-      alias -s org=emacs
-      (( $+commands[jq] )) && alias -g MJ="| jq -C '.'"  || alias -g MJ="| ${pkgs.python3}/bin/python -mjson.tool"
-      (( $+functions[zshz] )) && compdef _zshz j
-      [[ -n $INSIDE_EMACS ]] && \
-      function ff () {
-        print "\e]51;Efind-file $(readlink -f $1)\e\\"
-      }
-
-      export _Z_DATA="${config.xdg.dataHome}/z"
-
-
-      [ -n "$EAT_SHELL_INTEGRATION_DIR" ] && \
-        source "$EAT_SHELL_INTEGRATION_DIR/zsh"
-
-      # walk - terminal navigator (only available on desktop systems)
-      if (( $+commands[walk] )); then
-        export WALK_REMOVE_CMD=trash-put
-        export WALK_OPEN_WITH="nix:vim;go:vim;rs:vim;py:vim;sh:vim;bash:vim;zsh:vim;md:glow -p;txt:less -N"
-        function lk {
-          cd "$(walk --icons "$@")"
-        }
-      fi
-    '';
-    loginExtra = ''
-            # if [[ -z $DISPLAY && $TTY = /dev/tty1 ]]; then
-            #   # exec dbus-run-session sway
-      			# 	exec dbus-run-session niri-session
-            # fi
+      [[ -f "${config.programs.zsh.dotDir}/init.zsh" ]] && \
+        source "${config.programs.zsh.dotDir}/init.zsh"
     '';
     sessionVariables = {
       RPROMPT = "";
     };
-
-    shellAliases = {
-      mkdir = "mkdir --parents --verbose";
-      rm = "rm --interactive";
-      cp = "cp --interactive";
-      mv = "mv --interactive";
-      gcd = "cd (git root)";
-      # ls = ''exa'';
-      ll = "ls -l";
-      la = "ls -a";
-      l = "ls -lah";
-      # t = ''exa --tree --level=2'';
-      map = "xargs -n1";
-      k = "kubectl";
-      wget = "wget -c --hsts-file=${config.xdg.dataHome}/wget-hsts";
-      # cr, crl, crf are now scripts in pkgs/my/scripts/bin/
-    };
-
-    plugins = [
-      {
-        name = "kubectl-config-switcher";
-        src = pkgs.fetchFromGitHub {
-          owner = "chmouel";
-          repo = "kubectl-config-switcher";
-          rev = "5679aa70383cee93fc15351dd4895c29c90b78a5";
-          sha256 = "sha256-Aifa5ms2p/l0FkZE8Tep8QiDWUdfFfdKrTIbJNurxw4=";
-        };
-      }
-      {
-        name = "zsh-z";
-        src = pkgs.fetchFromGitHub {
-          owner = "agkozak";
-          repo = "zsh-z";
-          rev = "aaafebcd97424c570ee247e2aeb3da30444299cd";
-          sha256 = "sha256-9Wr4uZLk2CvINJilg4o72x0NEAl043lP30D3YnHk+ZA=";
-        };
-      }
-      {
-        name = "zsh-nix-shell";
-        src = pkgs.fetchFromGitHub {
-          owner = "chisui";
-          repo = "zsh-nix-shell";
-          rev = "v0.8.0";
-          sha256 = "sha256-Z6EYQdasvpl1P78poj9efnnLj7QQg13Me8x1Ryyw+dM=";
-        };
-      }
-    ];
+    # No plugins here — we use home.file to place them without auto-sourcing.
+    # Sourcing is handled by dots/config/zsh/tools/*.zsh
   };
 }