docs/convention.md

# Convention

My rules for what belongs in dotfiles and what doesn't. I write
this down so that when I'm tempted to commit something I'll
probably regret, I have something to argue with.

For the actual setup flow on a new machine, see
[docs/switching-machines.md](/src/dotfiles/docs-switching-machines-md/).

## What belongs

Anything that meets all three:

1. **I want it identical across every machine I use.** If the
   thing is identical everywhere, the repo is the canonical copy.
2. **It is text.** Binary blobs are not in scope.
3. **It is not secret.** No keys, no tokens, no passwords, no
   machine names that leak my network structure.

Examples that pass:

- `zsh` prompt config
- `tmux` keybindings
- `neovim` plugin list and configs
- `git` aliases, signing config, default branch settings
- `editorconfig`

## What doesn't belong

Anything that fails any of the above:

- **Machine-specific.** SSH `~/.ssh/config` host entries, wifi
  credentials, printer paths. These vary per machine.
- **Secrets.** API keys, tokens, SSH keys, GPG keys.
  Full stop. Separate vault.
- **Large files.** Nerd fonts, language server binaries, my
  neovim `undodir`. Manage those with the OS package manager
  or a dedicated tool.
- **Anything that takes long to install.** Compiling neovim from
  source is not a dotfile concern. Use a package manager.
- **Anything I might want to try without committing.** Drafts go
  in local untracked files (`~/.zshrc.local`, etc.).

## Per-host overrides

Three files I source if present, never tracked:

    ~/.zshrc.local
    ~/.gitconfig.local          # included by .gitconfig
    ~/.config/nvim/lua/local.lua

In each, I put per-machine things: GOPATH on servers, work email
on the work laptop, monitor-specific overrides.

    # .zshrc tail
    [[ -f ~/.zshrc.local ]] && source ~/.zshrc.local

This keeps machine-specific content out of git without complicated
inclusion rules.

## Idempotency

Everything in this repo must be idempotent. You should be able to
run `./install.sh` twice on the same machine and have the second
run do nothing surprising. Commit `4df2a88` added a prompt before
overwriting existing non-symlink files, which is the specific
form of that promise:

- If the target doesn't exist: symlink it.
- If the target is already the symlink we want: skip.
- If the target is something else: ask.

The `ask` path is the one I designed for carefully. Saying "yes"
overwrites; saying "no" leaves the existing file alone; saying
"diff" shows me the difference and re-asks. I wrote this because
past me once lost a hand-edited `.gitconfig` to a rogue symlink.

## Stow as the manager

Each top-level directory mirrors `$HOME`. `stow zsh` creates:

    ~/.zshrc               -> ~/.dotfiles/zsh/.zshrc
    ~/.aliases.zsh         -> ~/.dotfiles/zsh/.aliases.zsh

Stow refuses to overwrite existing files by default, which aligns
with our idempotency goal. The script `install.sh` wraps stow with
the prompt flow above.

I chose stow over alternatives (chezmoi, yadm, git bare repo)
because it's the dumbest thing that works and I can explain it to
someone in thirty seconds. The sticker price (GNU stow must be
installed) is fine: it's in every distro's base repos.

Commit `3fe1c24` stow-ified the whole repo. Before that it was a
pile of manual symlinks managed by an early install script;
stow's tree-based symlinking is strictly better.

## Editors: neovim, not vim

I keep a minimal `.vimrc` because sometimes I ssh into a box that
doesn't have neovim. The minimal `.vimrc` is sanity-preserving
only: tabs to 4 spaces, `set nohlsearch`, nothing plugin-adjacent.

The real config lives in `nvim/`. Plugins are loaded via
[lazy.nvim](/src/dotfiles/nvim-plugins-lua/) (`ae44b17` moved away
from packer). Lazy loads plugins on demand so startup time
stays low.

Rule: no language-specific files bigger than 30 lines in the
general config. Language extras go in `ftplugin/` so they only
load for that filetype.

## Shell: zsh

With [fzf](https://github.com/junegunn/fzf), [starship](https://starship.rs/),
and [fzf-tab](https://github.com/Aloxaf/fzf-tab) (`81d209c`).
Plugins are vendored in via a trivial loader; no Oh-My-Zsh, no
Prezto.

Rule: aliases live in `.aliases.zsh`, not `.zshrc`. Keeps the
`.zshrc` short enough to read in one screen.

## Tmux

Prefix is `C-Space` (`52c8f03`, was `C-a`). Copy mode uses vi
keybindings, yank sends to the system clipboard.

Rule: colors come from the terminal's theme. The `.tmux.conf`
does not hardcode 256-color values; they would fight my
terminal's theme of the week. I set `default-terminal` to
`tmux-256color` and let the palette be whatever is configured
upstream.

## Git

Signing: SSH signatures, not GPG. The key material is managed by
1Password's SSH agent, which means the public key lives in the
config but the private key lives in a vault.

Rules baked into `.gitconfig`:

- `pull.ff = only` (`7b0a91e`). Never implicitly create a merge
  commit on pull.
- `rerere.enabled = true` (also `7b0a91e`). Remember conflict
  resolutions.
- `push.autoSetupRemote = true`. Stop yelling at me about upstream.
- `init.defaultBranch = main`.

User fields (`name`, `email`) are deliberately not in the tracked
file; they are in `~/.gitconfig.local` which is sourced via
`include.path`.

## What I don't put in dotfiles

Things people sometimes put in dotfiles that I don't:

- **Installer scripts for tools.** I use Homebrew's Brewfile and
  apt's package list for "these programs should be installed".
  That's a parallel repo, not this one.
- **Desktop environment config.** I use whatever the OS gives me.
  Rice is a hobby I don't have.
- **A billion shell plugins.** Every plugin is a startup-time tax.
  I run fzf, fzf-tab, and starship. That's it.

## Update discipline

Rules:

- Every change gets a commit message in the form
  `<area>: <what I did>`. `zsh`, `tmux`, `nvim`, `git`, `install`,
  `docs`. See the repo log for the pattern.
- If I haven't looked at a file in six months, consider deleting
  lines from it. The repo has gotten shorter over time, not
  longer, which is the right direction.
- No tracked changes that only work on one machine. If I can't
  test a thing on at least two different OSes, it's probably
  machine-specific (see "per-host overrides").

## Bootstrap minimum

The minimum I need on a new machine before I can install dotfiles:

- git
- stow
- zsh
- ssh + valid key to clone this repo

Anything beyond that is installed after, not before. Keeps the
bootstrap story portable: any machine that has a shell and
network can pick up from here.