From eb15ddf87a00bf13deaaa25f5a4e108ac8adffeb Mon Sep 17 00:00:00 2001 From: dissimulo Date: Mon, 7 Sep 2026 10:28:38 -0700 Subject: [PATCH] tmux: track .tmux.conf.user (async statusline + stardate) --- .tmux.conf.user | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .tmux.conf.user diff --git a/.tmux.conf.user b/.tmux.conf.user new file mode 100644 index 0000000..c5c9754 --- /dev/null +++ b/.tmux.conf.user @@ -0,0 +1,76 @@ +# --------------------------------------------------------------------------- +# Statusline element-flicker fix (2026-08-19). +# Sourced at the end of ~/.yadr/.tmux.conf, so these override what it sets. +# Kept HERE so a yadr re-bootstrap cannot clobber it. +# +# SYMPTOM dissimulo reported: "the statusbar is having a seizure -- elements +# appear and disappear at random." NOT the whole bar blanking (a 30fps screen +# recording of the bar showed zero frame-to-frame variation, and a whole-desktop +# capture showed nothing changing near it). Individual ELEMENTS winking out. +# +# CAUSE: `#(...)` in a status format is a tmux JOB. While that job is being +# (re)started its expansion is EMPTY, so the element vanishes until it finishes. +# tmux re-runs those jobs whenever the status is redrawn and the job is not +# already running -- and status redraws are driven by PANE ACTIVITY, not by +# `status-interval`. With a busy TUI in the pane (a `claude` session) the jobs +# were being restarted several times a second, so the username and the IP +# flickered in and out constantly. Measured: `#(whoami)` and `#(tmux-net)` +# expanded EMPTY in 30 out of 30 consecutive renders. +# +# FIX: resolve both into tmux USER OPTIONS and reference those. `#{@user}` and +# `#{@net}` are plain variable lookups -- no job, so nothing can ever be +# half-started and render empty. Rendered text is byte-identical to before. +# @user - a username cannot change within a session: read it ONCE. +# @net - the IP CAN change, so refresh on a slow background loop +# (4 forks/minute instead of several a second). The loop exits by +# itself when tmux goes away, because `tmux set` then fails. +# +# Bonus, already measured: this also cut the tmux server from ~25% of a core to +# ~12% and eliminated the fork churn (17 execs/10s -> 0). +# +# DO NOT "simplify" #{@user}/#{@net} back to #(whoami)/#(~/.local/bin/tmux-net). +# That is the bug, not a tidier way of writing it. +# --------------------------------------------------------------------------- +run-shell 'tmux set -g @user "$(whoami)"' +run-shell 'tmux set -g @net "$($HOME/.local/bin/tmux-net)"' +run-shell -b 'while sleep 60; do tmux set -g @net "$($HOME/.local/bin/tmux-net)" 2>/dev/null || exit 0; done' + + +# These lengths are measured against the RAW format string, NOT the rendered +# width: tmux applies them as #{T;=/N:status-right}, truncating BEFORE the +# #[...] style directives are stripped. status-right renders ~46 columns but is +# ~129 characters of source. A cap below the raw length severs the string +# mid-way, which looks exactly like 'the right side of the bar disappeared'. +# Set here as well as in .tmux.conf so this file stays self-sufficient. +set -g status-left-length 451 +set -g status-right-length 451 + +set -g status-left '#[fg=colour235,bg=colour252,bold] ❐ #S #[fg=colour252,bg=colour238,nobold]❯#[fg=colour245,bg=colour238,bold] #{@user} ' +# The trailing %S is drawn in bg-on-bg (colour234 on colour234) so it is +# INVISIBLE, but it makes the status string CHANGE every second. +# +# WHY THAT MATTERS -- the second half of the bug: tmux only repaints the status +# line when its computed content DIFFERS from what it last drew. With the clock +# at minute resolution and @user/@net static, the string was byte-identical for +# up to 60s at a stretch; so when anything wiped the bar it STAYED wiped until +# the minute rolled over. That is the "sometimes the entire bar disappears" half. +# A once-per-second change forces a repaint, so a wipe self-heals within ~1s. +# It costs one redraw of one row and NO forks (that is only affordable because +# the #() jobs above became #{@user}/#{@net}). +set -g status-right '#[bold][#[nobold,fg=colour229]#h#[fg=default] / #[fg=colour229]#{@net}#[fg=default,bold]]#[nobold,fg=colour255] %-I:%M%P %d-%b-%Y #[fg=colour39]★ Stardate %Y.%j#[fg=colour255] ' + + +# ---- circled window numbers, stamped asynchronously ---------------------- +# The icon is computed ONCE per window change and stored as a window-level +# user option; the format below is a plain #{@icon} lookup. The old version +# nested ten #{?#{==:#{window_index},N},...} conditionals evaluated for every +# window on every redraw, and redraws follow PANE ACTIVITY, not status-interval. +# run-shell -b is backgrounded, so none of this sits on the render path. +set -g window-status-format '#[fg=colour235,bg=colour252,nobold] #{?#{@icon},#{@icon},#{window_index}} #{b:pane_current_path} #W ' +set -g window-status-current-format '#[fg=colour234,bg=colour39,bold] [#[fg=colour232,bold]#{?window_zoomed_flag,#[fg=colour228],} #{?#{@icon},#{@icon},#{window_index}} #{b:pane_current_path} #W #[fg=colour234,bold]] ' + +run-shell -b '$HOME/.local/bin/tmux-window-icons' +set-hook -g after-new-window 'run-shell -b "$HOME/.local/bin/tmux-window-icons"' +set-hook -g window-linked 'run-shell -b "$HOME/.local/bin/tmux-window-icons"' +set-hook -g window-unlinked 'run-shell -b "$HOME/.local/bin/tmux-window-icons"' +set-hook -g session-window-changed 'run-shell -b "$HOME/.local/bin/tmux-window-icons"'