Skip to content

dotfiles: Cut Zsh startup cost and stabilize session tooling - #31

Merged
Unique-Divine merged 13 commits into
mainfrom
ud/zsh
Aug 24, 2026
Merged

dotfiles: Cut Zsh startup cost and stabilize session tooling#31
Unique-Divine merged 13 commits into
mainfrom
ud/zsh

Conversation

@Unique-Divine

Copy link
Copy Markdown
Owner

dotfiles: Cut Zsh startup cost and stabilize session tooling

This checkpoint removes Oh My Zsh from the interactive startup path, keeps the
commands needed at the first prompt available, and moves expensive Goenv setup
behind Zinit Turbo with a first-use fallback. It also carries the branch's
clipboard recovery, Neovim polish, and Herdr tab-position work.

Rationale

The old shell loaded Oh My Zsh as a framework to obtain a small set of plugins.
That also loaded its general libraries and initialized Powerlevel10k twice.
Profiling found a larger cost in goenv init -, roughly 566 ms, even though Go
shims only need the Goenv paths already in PATH.

The shell should become usable before optional integration and completion work
finishes. That cannot come at the cost of a broken first command. Git aliases,
the z command, vi mode, and Go shims therefore remain ready at the first
prompt. The Goenv function has a synchronous first-use path for the narrow
window before its Turbo task runs.

Key Changes

  1. Replaced the Oh My Zsh framework with pinned Zinit setup. Powerlevel10k,
    Git aliases, and z load synchronously. jsontools, sudo, and syntax
    highlighting use Turbo.
  2. Added separate benchmarks for synchronous startup and input-ready prompt
    time, then recorded a 20-run result after five warmups.
  3. Deferred goenv init - through a linked local Zinit snippet while keeping
    Goenv shims in PATH and providing a first-use goenv trigger.
  4. Replaced per-terminal SSH agents with one systemd user service and made its
    socket path safe in both Bash and Zsh startup files.
  5. Added shell syntax checks to just health, moved Zsh-specific code into
    .zsh files, and documented the resulting setup.
  6. Included the branch's existing WSL clipboard recovery, Herdr tab-position
    support, Neovim Markdown and visual updates, and the guarded .nvmrc CLI.

1 - Shell startup and first-use behavior

old shell startup
  .zshrc -> Oh My Zsh framework -> P10k again -> goenv init - -> prompt

new shell startup
  .zshrc -> P10k + Git + z + Goenv shim paths -> prompt
                                                |
                                                +-> Turbo: Goenv integration,
                                                    jsontools, sudo, highlighting

early `goenv ...`
  temporary Zinit trigger -> load goenv-init.zsh now -> replay the command

zinit-install.sh pins the Zinit checkout and prefetches all remote plugins
during just sync. It also creates the linked local Goenv snippet without
sourcing it, so a fresh interactive shell does not print setup output or fetch
anything.

The Goenv snippet runs command goenv init -. The command prefix bypasses
the temporary trigger and prevents recursive loading. GOENV_ROOT, its bin
directory, and its shim directory stay in PATH, so go version keeps working
even before the full Goenv function and completions load.

The NVM wrapper remains deliberately separate. It loads only when the user
types nvm; this PR does not make node choose a project version implicitly.

2 - Measured result and benchmark boundary

20 measured runs, 5 warmups, local WSL machine

                         before Goenv Turbo   after Goenv Turbo   change
synchronous startup         2178.50 ms           481.34 ms       -77.9%
input-ready prompt          2696.66 ms          1012.85 ms       -62.4%

just bench-zsh --mode init measures the synchronous path before Zinit's
Turbo callbacks run. --mode prompt starts an interactive shell in a
pseudo-terminal and stops when ZLE accepts input. Neither number claims that
every deferred plugin has finished loading.

These numbers are local measurements, not a promise for every machine. They
are still a useful checkpoint because the before and after runs use the same
20-run, five-warmup method. The documentation includes the commands needed to
repeat them.

3 - Shared session state and configuration checks

old terminal -> starts its own ssh-agent and adds keys
new terminal -> exports /run/user/<uid>/ssh-agent.socket
                 -> systemd user service owns the agent for the session

ssh_setup remains an explicit command for adding the configured identity
files. New terminals reuse keys already present in the shared agent, avoiding
startup work while keeping Git SSH usable once the session has been prepared.

zshenv now sets XDG directories, editor defaults, the stable agent socket,
and GPG_TTY only when a terminal exists. The socket expression avoids the
Zsh-only nested parameter expansion that made Bash consumers fail. just health checks core shared files with bash -n and Zsh-only files with zsh -n.

4 - Other changes included in this branch

The branch also recovers the WSL clipboard backend after its PowerShell child
dies, adds configured tab positions to Herdr, and updates Neovim's Markdown
rendering, list continuation, cursor, and indent-guide appearance. The
.nvmrc utility lists pins below a selected root and only rewrites allowlisted
repositories unless --all is explicit. These changes are independent of the
startup work but are part of this checkpoint's comparison with main.

@Unique-Divine
Unique-Divine merged commit 32cea99 into main Aug 24, 2026
2 checks passed
@Unique-Divine
Unique-Divine deleted the ud/zsh branch August 24, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant