Skip to content

Release tooling, CI gates, MRU sort, and a list scrollbar - #1

Merged
dsaad68 merged 6 commits into
mainfrom
release-ci-and-mru-sort
Aug 18, 2026
Merged

Release tooling, CI gates, MRU sort, and a list scrollbar#1
dsaad68 merged 6 commits into
mainfrom
release-ci-and-mru-sort

Conversation

@dsaad68

@dsaad68 dsaad68 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Four changes, mostly independent. The first three came out of reading
alchemmist/lazy-tmux for ideas worth
borrowing; the fourth is a bug that showed up while testing them.

Release: goreleaser

.goreleaser.yaml replaces the hand-rolled for target in … loop in
release.yml. Same four platforms, but now tar.gz archives (carrying
kaku-tab.tmux and docs/ alongside the binary), a checksums file, a generated
changelog, and a Homebrew cask.

The binary keeps its plain name inside the archive — one unpacking to
kaku-tab_darwin_arm64 would install something kaku-tab.tmux's PATH lookup
cannot find.

A cask rather than a formula: goreleaser deprecated brews outright
(goreleaser check fails on it), and a cask also clears the macOS quarantine
attribute.

Before merging or tagging, publishing the cask needs two things that do not
exist yet:

  1. a dsaad68/homebrew-tap repo
  2. a HOMEBREW_TAP_TOKEN secret — a PAT with write access to it

Until both exist the release still succeeds and skips the cask (guarded on
skip_upload, tested with the variable unset and empty). The README's new
Homebrew section is wrong until the tap is created.

CI: lint and coverage gates

.golangci.yml, 11 linters, deliberately without revive's exported rule
it produced 30 findings demanding doc comments on thin tmux wrappers and
bubbletea interface methods, which fights how this tree comments (why, not
what). The 5 real findings are fixed: two unchecked errors in popup(), an
ST1022 on DefaultTitleFormat, and dead minInt.

.testcoverage.yml excludes cmd/, internal/tmux, internal/kaku
process wrappers whose unit tests could only assert arguments back at
themselves. Thresholds sit just under today's numbers so they ratchet rather
than block: total 40 (at 42.9), resolve 85 (at 90.8), ui 40 (at 44.7),
mru 80 (at 88.5). Each override regex was verified to actually bind.

CI gains lint, coverage, and goreleaser-check jobs. setup-go now reads
go.mod instead of a pinned 1.22 that no longer matches it.

Picker: @kaku-tab-sort

Takes tabs (default, unchanged), mru, or name.

The interesting part is that tmux cannot answer "where was I?"
#{window_activity} is output-driven, so a window running top is permanently
most-recent; #{session_last_attached} only moves on attach, which is exactly
what kaku-tab avoids doing when it retargets a tab. So internal/mru records
kaku-tab's own picks — into a tmux server option rather than a file, because
window ids mean nothing outside the server that issued them and a server option
lives exactly that long.

The window you are in is demoted one place. It always heads the history
(switching there is what recorded it), so leaving it at rank 0 would put the
cursor on a row whose Enter does nothing. pickHeaderWindow needed the same
treatment. Windows never picked keep index order behind the ranked ones, so a
fresh tmux server is identical to the old default.

Picker: scrollbar

A list running past the viewport looked exactly like one that ended there — the
rows just stopped at the frame. Both lists now carry a one-cell gutter with a
thumb sized to the fraction on screen.

Reserved always, blank when everything fits: a gutter appearing only on overflow
would re-budget every column the moment a query filtered a row away, and column
widths are a property of the table, not of the query. renderRow budgets
against a new rowWidth(); listWidth() keeps its old meaning for sizing the
preview.

Verification

  • golangci-lint run — 0 issues
  • go test -race ./... — pass; 18 new tests across internal/mru and internal/ui
  • goreleaser check + a real --snapshot build: archives correct, injected version prints, cask renders
  • Both sort modes and the scrollbar rendered against a live tmux server via the
    detached-session capture in CLAUDE.md

Left alone

go.mod says go 1.26.5 while the README says "Go 1.21+ to build". Pointing
setup-go at go.mod fixes CI either way, but which of the two is wrong is a
call about the project's support floor, not a CI detail.

🤖 Generated with Claude Code

dsaad68 and others added 6 commits August 11, 2026 15:43
Three independent changes, all borrowed in spirit from alchemmist/lazy-tmux.

Release: .goreleaser.yaml replaces the hand-rolled build loop in
release.yml. Same four platforms, but now tar.gz archives (carrying
kaku-tab.tmux and docs/ alongside the binary), a checksums file, a
generated changelog, and a Homebrew cask. A cask rather than a formula
because goreleaser deprecated `brews` outright, and a cask also clears
the macOS quarantine attribute. Publishing it needs a dsaad68/homebrew-tap
repo and a HOMEBREW_TAP_TOKEN secret; until both exist the release still
succeeds and skips the cask.

Gates: .golangci.yml (11 linters, deliberately without revive's
`exported` rule — it wanted doc comments on every thin tmux wrapper and
bubbletea method, which is not how this tree comments) and
.testcoverage.yml, whose thresholds sit just under today's numbers so
they ratchet rather than block. Fixes the five findings that turned up:
two unchecked errors in popup(), an ST1022 on DefaultTitleFormat, and
dead minInt. CI gains lint, coverage, and goreleaser-check jobs, and
setup-go now reads go.mod instead of a pinned 1.22 that no longer
matches it.

Sort: @kaku-tab-sort takes tabs (default, unchanged), mru, or name.
tmux cannot answer "where was I?" — #{window_activity} is output-driven
so a window running top is permanently most-recent, and
#{session_last_attached} only moves on attach, which is exactly what
kaku-tab avoids doing when it retargets a tab. So internal/mru records
kaku-tab's own picks, into a tmux server option rather than a file:
window ids mean nothing outside the server that issued them, and a
server option lives exactly that long. The window you are in is demoted
one place, or the top row's Enter would be a no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A list that continues below the frame looked exactly like a list that
ended there: the rows just stopped, with nothing to say the next session
was one keypress down.

Both lists now carry a one-cell gutter at the right edge, drawn as a
track with a thumb sized to the fraction on screen. The gutter is
reserved always, blank when everything fits — one that appeared only on
overflow would re-budget every column the moment a query filtered a row
away, and column widths are a property of the table, not of the query.
So renderRow now budgets against rowWidth (the list column less the
gutter) while listWidth keeps its old meaning for sizing the preview.

The thumb is floored at one cell; on a long enough list an exactly
proportional one rounds to zero and vanishes at the size where it
matters most. The scrollback search gets the same treatment, where it
matters more — a grep over every pane routinely returns hundreds of
hits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The movement keys have always been there — arrows, ^k/^j, PgUp/PgDn,
Home/End — and none of them were written down anywhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
^e drops sessions with no terminal tab, leaving only what is actually on
screen. tmux marks every window of a client-less session Detached, so
this removes whole sessions, header included, rather than punching holes
in one. The cursor is held on its row across the toggle: unhiding inserts
whole sessions above it, and without that the selection lands somewhere
unrelated and Enter goes where the user did not choose.

Deliberately not persisted between invocations the way @kaku-tab-preview
is — a filter that quietly survived would one day hide half your sessions
with nothing on screen to say why. @kaku-tab-detached 'off' starts it on
for people who want that, and an empty list now names the filter as the
reason instead of reading as "you have no sessions".

Two glyph fixes alongside it:

The active-pane marker gets a column of its own. Flush against the status
glyph, "●*" read as one smudged symbol; and since only the active row
carried it, that row's label, command and path all sat one cell right of
its neighbours'. Reserved on every pane row, the columns line up and the
markers form a clean vertical run.

The cursor is a solid bar rather than ▸, which was the same glyph as a
collapsed session's fold arrow two columns over — on a folded row the two
sat side by side meaning entirely different things.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The block bar sat flush against the frame's │ and the two merged into
one doubled edge. ▶ (U+25B6) is the same shape as the ▸ (U+25B8) a
collapsed session folds with but at a visibly heavier weight, so the
two stay distinguishable in the one place they appear together — a
selected, folded header — helped by two spaces between them, the accent
colour and the row tint.

Every candidate was checked at one cell in tmux before picking: several
right-pointing triangles are ambiguous-width and a few have emoji
presentations, and a marker that rendered double would shift every
column on the selected row and nowhere else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An arrowhead rather than another triangle: ▶ differed from the ▸ a
collapsed session folds with only by weight, and ➤ differs by shape,
which is what carries when the two meet on a selected folded header.

Already measured at one cell in tmux alongside the other candidates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dsaad68
dsaad68 merged commit cd65954 into main Aug 18, 2026
5 checks passed
@dsaad68
dsaad68 deleted the release-ci-and-mru-sort branch August 18, 2026 20:14
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