Sync is now self-hosted at atuin.jonnybarnes.uk, so swap mcfly's
sheldon plugin for atuin's own init hook in zshrc.zsh.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YaYP6TMTB5TzyYSWnpF5ZT
The fork's copies of long-lived branches (demo-20, totara-19, lms) drifted
over a thousand commits behind upstream because only the default branch was
ever synced. Fast-forward each branch present on both remotes straight from
upstream's ref and push them in one go; skip and warn on divergence, and leave
a local copy alone if it carries commits of its own.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nvim-tree already tracks the current buffer via update_focused_file and
marks it with its own cursorline, but NvimTreeCursorLine links to
CursorLine, which catppuccin sets a shade away from the mantle the tree
draws itself on. The marker was there and unreadable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Squeezing the 5h/7d group onto line one was what cost the reset times and
the pace figure at laptop width, and it made the layout jump between one
and two lines as the branch name or the cost changed width. Line two is
now the group's own at every terminal size, so both timestamps and the
burn rate fit; the ladder only starts giving things up below ~80 columns.
The bars also survive down to 35 columns now rather than being dropped
below 68, since a line of their own is all they need. Every rung of the
ladder is fit-checked, the last one included: with the floor that low, a
percentage the API reports in more digits than anyone expects would
otherwise have overflowed the line rather than dropped the group.
The two timestamps still cost ~10 subprocesses to format on a line that
redraws per keystroke, so that is skipped when line two provably cannot
show them -- an under-estimate, so the fit check stays the decider.
With nothing left to squeeze, WRAP_NARROW, the wrap band and the split
tier go: line one is the full tier down to 150, wide to 68, then narrow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The weekly limit resets every 7 days, but a 5-day week means the sustainable
burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line
said that, so staying inside the limit meant doing the division by hand.
Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what
is left divided by the working days still in the window, so it is the
envelope for today. trend compares usage against today's band - during
working day n of m, anywhere between (n-1)/m and n/m of the budget is on
track - and reports the distance outside it.
A band rather than a point because a point built from completed days expects
0% on the first working day of the window, so any usage at all reads as
overspending: 6% on a Monday morning showed a red arrow. The band is also
all whole-day granularity supports, and the sleep-aware glide in
statusline.burnrate.sh is what it would take to say more.
Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are
counted inside the reset window rather than assumed, so a window that starts
mid-week still divides correctly. The colour thresholds derive from
100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh,
which would call 13%/d healthy against a 20-a-day budget.
SL_NOW is a test seam for the clock. The cache-age checks deliberately stay
on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past
USAGE_CACHE_SECS and send a test to the network with real credentials.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The indicator read alwaysThinkingEnabled from settings.json, which Option+T
never writes - it toggles thinking for the session only. The key was also
absent here, so `// false` pinned the segment to the hollow "off" diamond
while thinking was actually on: an inverted indicator, not just a stale one.
Claude Code pipes the live state in as .thinking.enabled, alongside
.effort.level. Read both, in one jq pass since the line re-renders on every
redraw. Absent thinking means enabled, matching the renderer's own
`thinking:{enabled: lt !== false}`.
The effort level now replaces the static "thinking" label, so the segment
says something that changes: "◆ high" rather than a word that was there
either way. Unrecognised levels fall back to the old label, since the wrap
branch sizes line one from this string.
Test widths are usable widths, not raw terminal widths: the tiers are
computed after padding is subtracted, so a raw width would change tier
whenever statusLine.padding did. The usage cache is seeded for the same
reason width_test.sh seeds it - an unseeded cache makes the first render
fetch live usage over the network.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Side-by-side splits each pane to about half a laptop's width, which is too
narrow to read. Inline is GitHub's layout; compact folds the unchanged
regions away, and the gutter signs replace the pane position as the cue for
what changed. `t` and `gc` still toggle both per session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A failed fetch left the cache mtime untouched, so with no network every
redraw — and a redraw happens on every keystroke — tried curl again and
could wait --max-time 10 for it. Cutting the TTL from an hour to five
minutes made that start 55 minutes sooner, so it is worth fixing now.
The marker is a separate file rather than a touch of the cached
response: a cold /tmp has no response to touch, and that is exactly the
case with no stale data to fall back on, where the timeout is paid in
full with nothing to show for it.
It goes down before the request and is cleared when that lands, not
written afterwards. A fetch is in flight for as long as curl takes to
give up, and this cache dir is shared by every session, so redraws
starting inside that window are real rather than hypothetical: they now
serve stale data instead of each launching their own doomed request.
Every attempt writes it, so a repeated failure restarts the backoff, and
only an attempt does, so the redraws it suppresses cannot keep
re-stamping it and it always lapses.
Missing credentials stays outside the backoff — it costs no timeout, and
the redraw after a login should show the bars rather than wait out a
retry window it had no part in earning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 5h/7d bars were served from a cache with a one-hour TTL, so a bar
could sit unchanged for most of a 5-hour window. The TTL is now a named
config setting next to GIT_CACHE_SECS at 300s: worst case 12 requests an
hour, shared across every session through /tmp/claude.
The section header claimed "cached 60s" — inherited from upstream and
never true here — which is what set the expectation the code did not
meet. It now names the setting instead of a number that can drift.
The refresh is gated on the same width-tier check as the bars, so a
render that shows them is a render that refreshes them: the new tests
pin that from full width down to WRAP_FLOOR, and pin that one column
below it, where the bars are dropped, no request is paid for either.
Those widths are given in usable columns, as in the sibling width test,
so the suite does not depend on the padding in the live settings.json.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Git materialises a remote from the existence of any remote.<name>.* key,
so remote.origin.followRemoteHEAD makes `git remote` list an origin in
every repo without one - this dotfiles repo included. It reads as a
broken remote to anyone looking, and has now been misdiagnosed as
leftover cruft twice. Nothing is actually wrong: get-url still fails, so
the guards in git-sync and nvim's default_branch() hold. Say so in both
places someone would look.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
<leader>gd/gm cover uncommitted work and a whole branch, but neither
answers "what did that one commit on main do". <leader>gr opens the
explorer on one commit's files, defaulting to a sha under the cursor so
it works straight from a log or a commit message. <leader>gR lists the
commits HEAD has that a base branch does not, oldest first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
In wrap mode the branch is truncated to whatever the rest of line one leaves,
and "the rest" was a flat 56 columns. It is not flat: the token counts and the
cost figure vary with the session, and with a four-digit cost the real width is
57, so line one came out exactly one column over and the renderer clipped it.
Reachable at COLUMNS 73-81 with padding 2 — a narrow split pane.
Add up the segments that follow the branch instead, from the same values that
render them, and count the ahead/behind markers too, which the constant also
ignored. That needs the cost formatted and the token bar width chosen before
the git segment is built, so both move up; neither depends on anything in
between.
The budget is now exact rather than approximate, so the branch also gets the
columns the old constant was over-reserving when the cost was short.
Tests: the large-cost sweep only used the short branch 'main', where the
overflow cannot show. Repeat it with the long branch, which fails on the old
budget at usable 68, 69, 70 and 75 by exactly one column.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The measured ladder tried every single-line variant before considering a
second line, so whenever line one plus the full group did not fit — from
around 92 usable columns upwards, depending on how long the branch, cwd and
model names are — it emitted rl_bare (5h and 7d only) and silently dropped
the per-model (Fable) bar that is the main reason the group is worth
rendering. On a 110-column laptop the limit was invisible.
Reorder so a second line beats losing that bar: one line rich/mid/lean, then
wrap, and rl_bare only when WRAP_NARROW is false. Reset times and
extra-usage credits are still given up rather than wrapped for, which makes
the rendered content non-monotone in width; the comment on the ladder spells
that out.
Also stop the tests writing fixtures to the caches the live status line
reads. ~/.claude/statusline.sh is a symlink to the script, so a test run was
visibly rendering fabricated usage — a Fable bar at 10%, extra-usage credits
of $1234.56/$2000.00 — in whatever session happened to be open, and a run
killed before its trap fired would have left that in place for an hour. The
cache directory is now $STATUSLINE_CACHE_DIR (default /tmp/claude) and each
suite points it at a temporary directory, which also removes the
backup/restore dance and the deletion of the live git-status cache.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/usage reports a weekly limit scoped to a single model (Fable) that the
status line did not surface. It is absent from both the status line's stdin
JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always
null now; it lives in the usage API's .limits[] under kind "weekly_scoped".
Render it labelled by scope.model.display_name so it follows whichever model
the limit applies to, inside the 7d segment since both are weekly limits
sharing one reset time.
Fitting it exposed a problem with sizing by width tier. Tiers only know the
terminal width, so content that varies with session state — branch name, cwd,
model display name — could push a line past the edge and be clipped by the
renderer. Measure the assembled line instead (vis_len) and emit the richest of
four rate-limit variants that fits, on one line or two: with reset times, with
extra-usage credits, bars only, or bars without the per-model segment. Every
branch is fit-checked, including with wrapping disabled. Two lines render
correctly in the status line.
Cap the cwd basename as well: nothing else shortened line one, so a long
project directory could overflow it on its own.
Correct the usable width. Claude Code exports COLUMNS but applies the `padding`
setting on top of it rather than deducting it first, so a line sized to COLUMNS
is clipped; deduct padding on both sides plus a column of margin.
Sanitise payload data before rendering. printf %b interprets backslash escapes,
which is how the colour variables work, so a cwd or model name containing a
literal \n — or a real newline, which jq decodes from the JSON — would split
the line and break both the width measurement and the two-line guarantee.
Parse the usage payload in one jq pass rather than ten. The status line runs on
every redraw and per-field parsing had become the dominant cost; this brings a
render back to roughly what it was before (~155ms vs ~115ms parent), the
remainder being the reset times the tiered version did not show at this width.
Fields are read one per line rather than via @tsv: tab is IFS whitespace, so
`read` collapses runs of it and an empty field — no scoped limit, which is the
common case — silently shifted every later field along by one.
Tests cover the API payload shapes including malformed and hostile input, the
width invariants (never exceed the usable width, never more than two lines,
never wrap unnecessarily), and a sweep over widths, branch lengths, model
names, cwd lengths and extra-usage. They restore the live usage cache on exit,
and remove the fixture outright when there was no cache to restore — otherwise
a test run would leave fabricated usage figures live for an hour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BuildEmpire/Totara renamed its default branch from main to totara-20.
Nothing local noticed, because refs/remotes/origin/HEAD is written once at
clone time and remote.<name>.followRemoteHEAD defaults to `create`, which
only fills the ref in when it is missing. So `git default-branch` still
said main, as did nvim's diff-against-branch prompt.
bin/git-sync does the start-of-work sequence for a fork - fast-forward the
default branch from upstream, push it to origin - and works out which
branch that is by asking the server, not the cache. It sets the cached
HEADs from the answer, so everything reading that ref agrees afterwards.
It lives in bin/ rather than as an alias because git picks up git-<name>
on the $PATH as a subcommand, and this is more shell than a gitconfig
alias should hold. followRemoteHEAD = always is set for origin as well, so
an ordinary fetch keeps the ref current without running the script.
The nvim prompt now prefers origin, then upstream, then the remaining
remotes. It took the first remote alphabetically before, which in
be-edition means kdog - a colleague's fork - rather than anything
authoritative.
Verified against a fixture of bare repos whose default branch is
totara-20 and whose cached HEAD is stale: the branch is created tracking
origin when absent, fast-forwarded when behind, pushed to the fork,
refuses to merge when the local copy has diverged, and is a no-op on a
second run. The nvim prompt was checked in three real repos.
diffview was doing the job for reviewing a branch before merging it, but
not well enough. codediff renders diffs the way VSCode does - light
line-level backgrounds with deeper character-level highlights on top of
them - so what actually changed within a line is legible rather than
inferred.
Keymaps carry over one for one: <leader>gd opens the status explorer,
<leader>gh/<leader>gH the file and branch history, and <leader>gm still
prompts for a base branch and diffs rev...HEAD against it. <leader>gc is
folded into codediff's own quit action alongside q, since there is no
close command to map it to.
Its diffing is done in C, and the library is downloaded prebuilt on
first use rather than compiled, so there is no build step for vim.pack
to worry about.
Installed via vim.pack, matching the other plugins here. The pack is
named explicitly ('catppuccin') because the repo is called 'nvim', which
would otherwise be the plugin's name in the lock file and on disk.
flavour = 'auto' with background = { light = 'latte', dark = 'macchiato' }
so the flavour follows 'background', which the TUI sets from the terminal
on startup. It is required first from config/plugins/init.lua so the other
plugins load with the colorscheme already applied.
Verified headlessly in a single nvim: `set background=light` gives
catppuccin-latte and `set background=dark` gives catppuccin-macchiato, so
a running instance can be switched by hand with `:set background=` as the
top-level README already suggests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gd only diffs the working tree against the index, so it shows nothing
once a branch's work is already committed - found this reviewing a
fully-committed branch, where it just showed blank panes.
gm prompts for a rev and opens DiffviewOpen <rev>...HEAD instead. The
prompt defaults to whatever the first remote's HEAD points to, since
the base branch name isn't consistent across repos (main here,
develop in jonnybarnes.uk).
Verified against jonnybarnes.uk's micropub-token-revocation branch:
default_branch() correctly resolved forge/HEAD to develop there vs
main in this repo, and DiffviewOpen develop...HEAD listed all 25
changed files.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013t7aQbc4EyYtab1crvSFTd
Installed via vim.pack, matching how the other plugins here are
managed. Adds a new <leader>g "diff" group: gd opens a Diffview
against the index, gc closes it, gh shows history for the current
file, gH for the branch.
Verified headlessly: config/plugins loads clean, :DiffviewOpen is
registered, and opening/closing a Diffview against this repo works
with no errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013t7aQbc4EyYtab1crvSFTd
prefix S now prompts for a name and runs new-session directly, replacing
the `<prefix> :` then `new -s name` command-mode round trip.
Session numbering in choose-tree (prefix s) turned out to be
session_id, an internal per-server counter with no base-index
equivalent, so it can't be made to start at 1 the way windows do;
named sessions plus C-s search in the tree are the workaround.
Also fixed a bug found while testing: `<prefix> r` re-sources the
whole config, and the trailing `source-file ~/.tmux-dark.conf` at the
end always ran, clobbering a light-themed session on every manual
reload. It now checks the invoking client's theme like the
client-attached hook already does.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013t7aQbc4EyYtab1crvSFTd
Two nvim-tree defaults that were quietly getting in the way.
filters.git_ignored defaults to true, so gitignored files are hidden. The
symptom was .env being absent from a project while .env.example sat right
there, which reads as a dotfile filter but is not: filters.dotfiles is already
false, so dotfiles show. The filter is git-aware, and .env was simply reported
ignored. Verified in a throwaway repo ignoring .env and vendor/ — both appear
now, and neither did before, with the ignored glyph to distinguish them.
The trade-off is real and is the reason for the default: vendor/ and
node_modules/ now show too. filters.exclude = { '.env' } was the narrower
option, exempting one name while keeping the rest hidden. Chose the broad
setting deliberately; `I` in the tree hides them again per session.
update_focused_file.enable defaults to false, so the tree never moved when a
file was opened from fzf-lua. Enabling it reveals the current buffer on
BufEnter, uncollapsing folders to reach it. Verified on a nested fixture:
opening src/deep/nested/target.php expanded all three folders and put the tree
cursor on the file. Focus stays in the file window, so it reveals rather than
steals, and it follows every buffer switch, not just fzf-lua.
Left update_root off. It would re-root the tree at files outside the current
root, which makes the tree wander into vendor/ — worse now that ignored files
are visible and easier to open by accident.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keymaps are discoverable in-editor by pressing <Space> and pausing, and are
written down separately, so they are deliberately not duplicated here. What was
not recorded anywhere was the surrounding context.
Covers the 0.12 floor, since autocomplete, pumborder, pummaxwidth, the nearest
completeopt value and vim.pack all require it; what bootstrap.sh symlinks where,
and why config/ is linked in as lua/config; and the vim.pack convention of one
file per plugin with versions pinned in the tracked lock file.
The rest is the behaviours that read as bugs until you know they are deliberate.
A standalone .php file gets no LSP at all, because phpactor sets
workspace_required and wants a project marker. Spell checking is on globally, in
code buffers too. netrw is disabled so `nvim .` opens nvim-tree, on the right.
Diagnostic signs are letters rather than icons.
Also restates the gitsigns global-mapping rationale already in the source, since
folding those maps into on_attach is the obvious tidy-up and it silently stops
them appearing in the which-key popup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nine colours were hardcoded One Dark RGB via 38;2;R;G;B, so they did not
follow the light/dark switch. Measured against the tangere backgrounds they are
fine on dark (4.25-10.80:1) but collapse on light: orange 1.78, amber 1.70,
yellow 1.63, and 'white' — used for the token total — at 1.35:1, effectively
invisible.
Switched to palette indices, which resolve through the terminal's own palette and
so follow the ghostty tangere-light/tangere-dark swap with no detection and no
cost per render. 'white' becomes 39, default foreground, since it means primary
text. All seven colours actually used get distinct indices; blue and yellow are
unreferenced but converted too, so no RGB is left behind.
Contrast now clears WCAG AA in both modes: 5.77-14.26:1 on dark, 6.32-20.61:1 on
light.
Trade-off: tangere has no orange and its palette 2 is teal rather than green, so
'green' reads teal and the warm colours sit closer together than the One Dark
originals did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tested with the system actually in light mode rather than inferred from dark:
- the status bar switches in the same second client-light-theme fires
- a fresh nvim reports background=light, so the OSC 11 round trip through tmux
to ghostty works and restarting nvim is genuinely sufficient
- delta resolves syntax-theme = GitHub, its light default, against Monokai
Extended on dark, so it does detect the terminal background
Both the nvim and delta checks needed a real tty to be meaningful. Run with
stdout on a pipe they each fall back to their dark default, which looks like a
correct answer while proving nothing.
Also documents why the first Light switch did not move the status bar: tmux
enables the terminal's theme-reporting mode when a client attaches, so the
client that predated these hooks was never asked to report changes. A detach and
reattach fixes it, and a fresh server never sees it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The status bar was five hardcoded tangere-dark colours, so in light mode the
pale #fdfdd9 text landed on a white background and became unreadable. That was
the real reason Auto appearance "never worked with the terminal setup" — ghostty
was switching correctly all along.
Split the colours into tmux-light.conf and tmux-dark.conf. The dark file is
extracted byte-exact from the previous config; the light file is the same layout
with each colour swapped for the tangere-light palette entry at the same index,
so roles are preserved (text on an accent uses the theme background, which
inverts from #1a2938 to #fdfdfa).
tmux 3.6+ learns the terminal's theme over OSC 2031, so this keys off the
appearance change itself rather than a schedule, and behaves identically whether
that came from Auto at sunrise/sunset or a manual toggle. Three hooks: the two
theme hooks react to a change, and client-attached covers a client attaching
mid-way, since those two only fire on a change. Sourcing is idempotent so the
overlap is harmless.
This replaces the dark-mode-notify launchd agent removed in 59d5622, which could
not have worked: it re-sourced .zshrc inside each shell, but the status bar
belongs to the tmux server.
Verified by round trip: flipping to Light switched #{client_theme} and applied
the light palette, and returning to Dark restored it. A running nvim does not
follow a live change (docs: the TUI sets 'background' on startup); noted in the
README.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The launchd agent ran `pkill -usr1 zsh` on every appearance change, which made
every shell re-source .zshrc. That could never have achieved its goal: the tmux
status bar belongs to the tmux server and colours to a running nvim, so
re-sourcing a shell rc cannot retheme either. The one variable it set,
MACOS_APPEARANCE, was read by nothing. Its only real effect was firing on each
unlock — 5253 times on this machine, 3437 on the iMac — which is what drove the
~/.gitconfig.lock contention fixed in 583bbaa.
The agent and plist are removed from both machines; this drops the script.
ghostty, bat, delta and nvim all handle appearance natively, and tmux 3.6+
detects it over OSC 2031 (#{client_theme} already reports correctly). Wiring the
tmux client-light-theme/client-dark-theme hooks is left as the next step.
Also refreshes the stale parts of the README: the intro still explained
~/.gitconfig as a copy because .extra edited it, and the .extra example still
showed the `git config --global` calls that caused the lock errors, plus the
misspelled GIT_COMMITER_* vars. Following it would have reinstated the bug.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
~/.extra ran five `git config --global` writes on every shell startup, to
re-assert identity and signing after bootstrap.sh copies gitconfig over
~/.gitconfig. The dark-mode-notify agent fires on wake/unlock and runs
`pkill -usr1 zsh`, so every shell re-sources .zshrc — and therefore .extra —
simultaneously. `git config` locks with O_EXCL and has no retry or timeout for
config files, so the concurrent writes raced and printed:
error: could not lock config file /Users/jonny/.gitconfig: File exists
Identity and signing now live in an untracked ~/.gitconfig.local, included
last from gitconfig so it wins over anything above. bootstrap.sh seeds it from
gitconfig.local.template only when absent, so re-runs never clobber the real
signing key. No git writes happen on shell startup at all now.
Verified: fully-resolved config is unchanged apart from the new include.path;
author and committer both resolve from config with no GIT_* env vars set;
existing commits still verify. 12 concurrent sources of .extra are silent,
where the old version produced 27 lock errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Netrw is disabled, as nvim-tree needs to own directory buffers so
`nvim .` opens the tree rather than the two fighting over it. That
removes :Ex, so the Obsidian guide has been updated to point at
<leader>e instead.
No auto-open autocmd: `nvim <path>` opens just the file, and the tree
is there on <leader>e when it is wanted. Placing it on the right also
flips the split direction, so opening a file from the tree puts it to
the left rather than displacing the tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wraps the fzf binary already installed via brew, alongside rg and fd,
so it needs no plugin dependencies - unlike telescope, which would
also pull in plenary. Picks up nvim-web-devicons for file icons, which
is the first thing actually using that plugin.
Maps live under <leader>f: files, live grep, buffers, grep the word
under the cursor, and help tags. The intent is to stop treating netrw
as the way into a project and switch to jumping by name, leaving
buffers open and bouncing between them, rather than quitting out of
nvim to change file.
Netrw is untouched and still on :Ex for browsing an unfamiliar tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gitsigns keymaps were defined in on_attach, so they were
buffer-local and created asynchronously - gitsigns shells out to git
before attaching. which-key had already built its keymap tree for the
buffer by then, and it only invalidates that cache on BufReadPost,
BufNew and LspAttach, so maps added afterwards stayed invisible.
Buf:get() returns cached mode state unless explicitly passed `update`,
which is why re-entering the buffer didn't help either.
Mapping globally instead means the maps exist before which-key builds
any tree. The gitsigns API no-ops in buffers it hasn't attached to
(checked stage/reset/preview/blame/nav in a non-git directory), so the
only change is that the maps now exist everywhere rather than just in
git-tracked buffers.
Also puts the diagnostic float and quickfix list on <leader>d. Nvim
already binds <C-W>d and ]d/[d for these, so this is a rebinding for
discoverability rather than new capability.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
which-key needs an icon backend and treats mini.icons and
nvim-web-devicons as equally viable optional deps, so devicons covers
it. Both are pure APIs with no visible effect on their own; devicons
self-initialises on require, so no setup() call.
Group labels for the <leader>h and <leader>t prefixes live in the
which-key config rather than next to the gitsigns maps they describe,
keeping which-key's config in one place. Worth moving if more plugins
start contributing prefixes.
The Nerd Font the glyphs need was installed by hand on this machine
but missing from brew.sh, so a fresh bootstrap would have rendered
every icon as tofu. Records it as a cask alongside the ghostty
font-family setting that already expects it.
Also drops the claude-code cask, which was never actually installed
here: brew tracks stable releases, and we want newer ones, so it comes
from Claude's own installer instead. Left a comment so it doesn't get
re-added.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restores plugin management after the v0.12 config rewrite dropped the
earlier `vim.pack.add` calls. Plugins now live in `config/plugins/`,
one file per plugin, so adding the next is a single require.
Sets `<leader>` to Space for the gitsigns hunk mappings, and pins
'signcolumn' open so git and diagnostic signs don't shift text.
The lockfile moves into the repo and is symlinked by bootstrap.sh, as
the vim.pack docs recommend, giving reproducible plugin revisions
across machines and a rollback path via git. vim.pack writes it with a
truncating open, so it follows the symlink rather than replacing it.
Drops the orphaned nvim-tree entry, unreferenced since the rewrite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude Code CLI warns when focus-events is off. The option was already
present in the config but commented out from when it was first added.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add statusline.isaacaudet.sh and point the bootstrap symlink at it,
keeping the burnrate and original variants alongside.
The script already had a 7d rate-limit bar but gated it behind the
`full` width tier, so it only appeared at >=150 columns. Ungate it so it
renders wherever the 5h bar does, mirroring it exactly. The `full`-only
extra_usage bar is unchanged.
To pay for the ~14 columns that costs, the reset timestamps are now
`full`-only (the 5h one previously also showed at `wide`), and the model
name is shortened at `split` as well as `narrow` - with the 7d bar added
a `split` line came to 82 visible columns against a 76-column budget and
wrapped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`brew install gnu-sed --with-default-names` fails with "invalid option"
- Homebrew dropped per-formula option flags years ago. The flag existed
to install GNU sed as `sed` rather than `gsed`, which is now the
opposite of what we want, so drop it and note why in the comment.
`oven-sh/bun/bun` no longer resolves; bun has since moved into
homebrew-core, so install it by its plain name. This line has been
failing on every bootstrap, which is why bun was never actually
installed.
Neither aborted the script (there is no `set -e`), they just failed
their own install and carried on. Verified all 47 formulae and 6 casks
now resolve and none are deprecated or disabled.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prepending the coreutils/findutils/gnu-sed/grep gnubin dirs put GNU
stat, date, sed, grep and find on the PATH under their un-prefixed
names, which silently breaks any script written against the macOS
versions. Portable scripts typically probe BSD-first and fall back to
GNU, so the BSD probe succeeds against a GNU binary that means something
else entirely and the fallback never fires.
The Claude Code statusline was a live example: it reads a cache file's
mtime with `stat -f %m`, which GNU stat parses as a *filesystem* format
string. That emits a multi-line blob, the following arithmetic hits a
syntax error, and because an arithmetic error aborts the shell the
enclosing function returned early - so the git branch segment silently
vanished on every warm-cache render. Its `date -j -r` reset timestamps
failed the same way.
Homebrew installs the g-prefixed variants into $HOMEBREW_PREFIX/bin
regardless, so gstat / gdate / gsed / ggrep / gfind still give GNU
behaviour on demand. Note BSD sed needs `sed -i ''` rather than `sed -i`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On the server, fnm isn't installed since system nodejs is used instead.
The zsh-fnm plugin's own fallback silently curl-installs fnm when missing,
and its --use-on-cd hook then prompts on every cd since no default version
is fnm-managed there. Gate the plugin to Darwin only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3jcSVn51hiw58f7jPWrpN
Hue 226 (#ffff00) has a contrast ratio of 1.05 against the tangere-light
background (#fdfdfa), below the minimum-contrast = 1.1 set in the ghostty
config. Ghostty then substitutes the foreground for whichever of black or
white has more contrast, so against a near-white background that letter of
the model name rendered black.
Swap 226 for 184 (#d7d700, ratio 1.51 light / 9.60 dark) in the Opus ramp
and the unknown-model fallback, the only two palettes that used it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keep the previous statusline as statusline.original.sh and add
statusline.burnrate.sh from Gui-Gou/claude-statusline-burnrate, which
shows weekly/5h plan usage and a sustainable burn rate.
bootstrap.sh now links the burnrate script unconditionally - the old
`test -L` guard would have left an existing symlink pointing at the
previous script. jq is a hard dependency of the new script, so add it
to brew.sh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>