<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/docs/config.md, branch master</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/docs/config.md?h=master</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/docs/config.md?h=master'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-09-22T13:12:02Z</updated>
<entry>
<title>osc 5577: `busy`, and a sidebar that shows it (busy_mark)</title>
<updated>2026-09-22T13:12:02Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T13:12:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=720aa48e249c0c962e8f73372ceded6092414a6e'/>
<id>urn:sha1:720aa48e249c0c962e8f73372ceded6092414a6e</id>
<content type='text'>
A status is a line of text, and a line of text cannot say whether it is still
true. "building 3/7" reads exactly the same while it builds and ten minutes
after it stopped, which is the one thing somebody reading it from another tab
wants to know -- and the only workaround available to a program was to write
the answer into the words and keep rewriting them.

So `busy`:

    printf '\033]5577;1;status;make test\033\\'
    printf '\033]5577;1;busy;1\033\\'
    ...
    printf '\033]5577;1;busy;0\033\\'

A flag of its own rather than a field of `status`, because a status is the
*whole* payload after the verb -- `status;a;b;c` has always been the text
"a;b;c" -- so anything added to that line would change what every existing
sender means, including the five pi extensions D1 promises byte compatibility
to. Orthogonal, so a program can leave one line up and toggle the other;
anything but 0/false/no/off is on; `clear` takes both; and a pane whose program
has gone is never busy, whatever it last claimed, because a spinner turning
under a dead tab is a lie you keep looking at.

In a sidebar the row turns `tab_status_busy` -- the theme's accent, derived
like the other status colours, so no theme needs a line -- and takes a spinner
in the column `tab_bar_padding` was already holding blank, so nothing reflows
when a program starts or stops working. `busy_mark` is the frames, separated by
spaces: one frame is a static mark and costs nothing, several make it turn, and
only then does the session keep a frame clock -- via the same `animating` flag
an animated shader uses, so the cost lands only while such a row is actually
composed, and `""` drops the mark entirely and leaves the colour to say it.
The frame is a pure function of the clock, like every other pulse here, so two
clients attached to one session show the same one.

`panes` now reports `status` and `busy`, which is the scriptable half of the
same fact: what a pane says about itself, without reading its screen.
</content>
</entry>
<entry>
<title>sidebar: air inside it, the way panes have padding (tab_bar_padding)</title>
<updated>2026-09-22T13:01:07Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T13:01:07Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a'/>
<id>urn:sha1:43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a</id>
<content type='text'>
`padding` is a pane's: the ring between a frame and the text it holds, in rows
times gap_aspect so a `1` looks square around a block of prose. Handing the
same knob to the sidebar looked tempting and is wrong twice over. The sidebar
is chrome, not a pane, and its width is fixed by tab_bar_width -- so `padding
1` would quietly eat four of a sixteen-column strip (two cells a side, aspect
included) and shorten every tab name, because somebody wanted their terminal
text to breathe. And its two axes are not one idea: the columns are an indent
for labels, the rows are air above a list.

So tab_bar_padding, its own knob in the family the rest of the sidebar's air
already lives in (tab_bar_pad, tab_gap, newtab_pad), taking the same 1/2/4
CSS-order values as `padding` -- one reader for both, so they cannot drift
into meaning different things -- but counted in cells.

The default `0 1` is what the sidebar already drew: that one column is where
every label's leading space has been hard-coded since the sidebar existed, and
moving it into a setting is what makes `tab_bar_padding 0` possible at all --
names flush against the frame, which was not previously a thing anyone could
ask for. The statuses, the `+ new tab` row and the pane count follow the same
indent, since they were each carrying their own copy of that space.

It is air *inside* the plate: a tab's fill still spans the whole row and the
whole row is still the hit, because the row is what you click and a highlight
that stopped short of the frame would read as a highlighted word rather than a
chosen row. The bottom rows are given up when the strip is short of them, like
every other piece of air in here.

tab_bar_pad is the older name for the top component alone. It still works,
still means exactly that, and wins where a config writes both -- it is the
more specific of the two. The dump writes tab_bar_padding and mentions it.
</content>
</entry>
<entry>
<title>sidebar: the tab number at the far edge (tab_bar_index)</title>
<updated>2026-09-22T11:14:24Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T11:14:24Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=24bfcc26df376621ccfe1e695bb4f5e6f1807e56'/>
<id>urn:sha1:24bfcc26df376621ccfe1e695bb4f5e6f1807e56</id>
<content type='text'>
`1:api` puts a number at the start of every row, and in a sidebar that is a
column of digits in front of the thing you are actually reading -- the names
start one, two or three cells in depending on how many tabs you have, and the
numbers are only wanted at the moment you reach for C-a 2.

tab_bar_index "right" moves them:

    │ tests        1 │        │ 1:tests        │
    │ verylongproj 2 │   vs   │ 2:verylongproje│
    │ api          3 │        │ 3:api          │

Every name starts in the same column and the numbers line up in one of their
own, which costs a column of sidebar and buys the list a shape. "prefix" stays
the default, because it is what the strip has always drawn and the strip is
where most people meet it.

Sidebars only, and not by omission: a tab in the top strip is exactly as wide
as its label, so there is no far end to align against and a number floating
after a name it does not belong to would be worse than the prefix it replaced.
The setting is ignored there rather than half-honoured. A row too narrow to
spare the columns (under the number plus five) also keeps the prefix, since
the name is the part you came for, and the rename editor keeps the whole cell
as it always did.

A name long enough to reach the number stops one cell short of it, because
`verylongproje2` is a tab called verylongproje2 as far as any reader can tell.
The cell comes back as padding, so the plate is unbroken and the hit is still
the whole row -- clicking the number is clicking the tab, and the number
brightens with the label under the pointer rather than leaving half a row lit.
The bell mark keeps its place between them, budgeted first as before, so a
long name cannot eat either of the two things that are not the name.
</content>
</entry>
<entry>
<title>themes: ask the binary where it is, not the prefix it was built for</title>
<updated>2026-09-22T10:57:26Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T10:57:26Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=68ff62e9492accd4c1c9a83edbcbd225afb1a11a'/>
<id>urn:sha1:68ff62e9492accd4c1c9a83edbcbd225afb1a11a</id>
<content type='text'>
`theme_name "sl0p"` with all seven themes sitting in /usr/share/slosh/themes:
"no theme named sl0p". The lookup knew two places -- your own themes directory
and the PREFIX this build was compiled with -- and on that machine the first
did not exist and the second was /usr/local, because that is the Makefile's
default and the package was built with PREFIX=/usr. Neither was where the
themes actually were, and nothing in the search could ever have found them.

A compiled-in prefix is a guess about where the binary will end up, and the
binary is the one thing that knows where it really is. So the search now asks
it: path_self() (/proc/self/exe, _NSGetExecutablePath, GetModuleFileName --
lifted out of server.c, which was already doing this for SLOSH_BIN) gives
&lt;bin&gt;/../share/slosh/themes for an installed slosh wherever it was installed
to, and &lt;repo&gt;/contrib/themes for one being run out of a build tree, which
makes a checkout able to name a theme without installing anything first. The
compiled-in prefix stays as the last candidate, for a binary sitting somewhere
that says nothing about its data, and duplicates collapse so the usual install
does not report the same directory twice.

Your own directory is still first and still shadows everything, which is the
property that matters; the rest is about the shipped set being findable at all.

The tests that assumed a closed world of two temp themes now narrow to their
own names by typing, since the shipped ones are (correctly) always in the list
-- and the "no themes anywhere" toast lost its test, because with the shipped
set reachable that state takes a binary with nothing beside it. The case that
started this is a test of its own.
</content>
</entry>
<entry>
<title>theme: a name and a directory, not sixty colours in your config</title>
<updated>2026-09-22T10:42:15Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T10:42:15Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=faa3fb6ee575a900abc574b0f9ed5ff291761837'/>
<id>urn:sha1:faa3fb6ee575a900abc574b0f9ed5ff291761837</id>
<content type='text'>
Wearing a theme meant pasting its `theme { }` block into config.kdl, or
including a path to a file you had copied there yourself. Both make the
palette a literal in your config, and a literal is the one thing you cannot
change cheaply: not from a script, not from a running session, not by a list
of what is available, and never again from upstream -- the colours you pasted
are the colours that theme had the day you pasted them.

So a theme is a file in a directory now, and a config names it:

    theme_name "phosphor"
    theme { frame_focus "#00ff88" }   // ...but that one colour is mine

theme_name is applied after the includes and before everything the file says
itself, which is the rule includes already follow and for the same reason: the
loader reads a document by asking it for names rather than walking it in order,
so "here" is not a position it could honour, and "what you asked for is the
base, what you wrote is yours" is the rule worth having.

Two directories, yours first: `themes/` beside the root config -- beside the
file that was loaded, so a config somewhere else (SLOSH_CONFIG, or one handed
to --check) takes its themes with it -- and then &lt;prefix&gt;/share/slosh/themes,
compiled in from PREFIX. install(1) has been putting themes there all along
and nothing could read them; now a shipped theme is nameable without being
copied, and a file of yours with the same name shadows it, which is how a
root-owned theme gets edited.

A name, not a path. The directory is what makes a theme switchable, listable
and shadowable, and a path silently does none of those, so `theme_name
"../x.kdl"` is refused with a line pointing at `include`, which still works
and is still the answer for a file somewhere a directory of names cannot
reach. A theme that is not there is one complaint and no more (D9): the rest
of the config applies, because losing your keybindings over a mistyped colour
scheme would be the wrong trade. A theme file is read by the same loader, so
it is watched like any other: saving one reloads the session wearing it, and
the derivations (tab_status_fg, tab_idle_bg) run over the theme's own colours
exactly as before.

Switching without editing anything, over the socket a session already has:

    slosh -s main cmd theme                                # amber default* ...
    slosh -s main cmd '{"cmd":"theme","name":"amber"}'      # now
    slosh -s main cmd '{"cmd":"theme","name":"amber","save":true}'

The session holds the name over later reloads, so saving your config for an
unrelated reason does not put the old colours back; `save` writes the
theme_name line into the file, replacing the one already there and leaving
every other line and comment alone -- dumping the whole config back over
itself would reformat a hand-written file and drop its comments, which is not
a fair price for changing a colour scheme. A name is tried on a throwaway
config first, so a typo costs nothing. An empty name gives the answer back to
the file.

--dump-config stops writing colours: it writes the theme's name and the
colours a config overrode by hand, so a dumped config keeps following its
theme instead of freezing it. --dump-theme is the other half and is new: the
resolved palette as a theme file, which is how you start one from the session
in front of you rather than from an empty file. It is also what keeps
contrib/themes/default.kdl honest -- that file is now the annotated reference
(the commented palette moved there out of config.kdl, where it was 119 lines
of colour in the middle of the prose), and a test compares it against the
compiled-in one.

While in there: tests/test_themes.py's "every theme sets every colour" guard
had been grepping for `&amp;c-&gt;`, the spelling before the colour table moved to
offsetof, so it had been finding zero colours and passing for months. Fixed,
with the derived and opt-in colours exempted for the reasons they are derived
and opt-in, and with a check that the guard found a table at all.
</content>
</entry>
<entry>
<title>sidebar: air between tabs (tab_gap)</title>
<updated>2026-09-20T18:23:23Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-09-20T18:23:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=07e1adf1124657aaea4fa32874282812281e4889'/>
<id>urn:sha1:07e1adf1124657aaea4fa32874282812281e4889</id>
<content type='text'>
A tab carrying three rows of wrapped status and the tab under it carrying
three more read as one column of text: the labels are in there, but the list
has stopped looking like a list. tab_gap is the blank rows between them, 0 by
default, which is exactly what the sidebar has always drawn.

It is the space *between* tabs and nothing else. Above the first tab is
tab_bar_pad's and before the button is newtab_pad's, so the gap is applied
before each label except the first and none of the three is ever paid twice.

Given up a row at a time when the list is short of room, ahead of every label
and the button, which is the same order the pad and the statuses already
follow: a gap that pushed a tab off the bottom would be spacing costing you
the thing it spaces. Paint rather than layout, so tuning it repaints without
reflowing any pane. Sidebars only -- the top strip is one row, and the space
between tabs there is the label's own padding.
</content>
</entry>
<entry>
<title>sidebar: a status can have more than one row (tab_bar_status_lines)</title>
<updated>2026-09-20T17:55:03Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-09-20T17:55:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=65a694b194bd68f8c6ba4b961e2cc906b8738a6c'/>
<id>urn:sha1:65a694b194bd68f8c6ba4b961e2cc906b8738a6c</id>
<content type='text'>
Sixteen cells is not enough for a sentence, and a sentence is what a pane
has to say: "add a summary…" is most of a status and none of its meaning.
tab_bar_status_lines 3 word-wraps instead of cutting, and the same column
carries forty-eight cells rather than sixteen.

Wrapped, the rows under a tab stop being a list of panes and become a
paragraph each, so the shape no longer says where one pane's status ends and
the next begins -- both are italic text down one column. Alternate panes are
drawn on alternate bands to say it instead: tab_status_stripe, derived from
the theme like tab_status_fg, so a theme that has never heard of any of this
still gets a band in its own family. Nothing is painted at one line, where
there is no ambiguity to resolve and a band would take a translucent
terminal's background away for nothing.

1 is the default and is the old behaviour exactly, down to cutting at the
column rather than the last word before it: with no next row to move the word
to, breaking early would spend cells on nothing. The reason to prefer a word
boundary only exists once there is somewhere for the word to go.

Rows count against tab_bar_status as before, so wrapping trades panes for
words within the same allowance, and the allowance itself still gives way to
the tabs. A status that will not fit whole is dropped whole and reported with
the ellipsis, rather than shown as a fragment that reads like the whole
thing. Every row of a status is the same `find:` door.

tabstatus's buffer goes to 256 to match pane_status's own slot: at one line
the drawing cut it anyway, but wrapped it is the whole string that has to
survive the trip.
</content>
</entry>
<entry>
<title>tabs: air before the new-tab button, and the option to do without it</title>
<updated>2026-09-20T17:40:05Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-09-20T17:40:05Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=6c8d99cd71858955c0e9c9d93d738c855e652ce8'/>
<id>urn:sha1:6c8d99cd71858955c0e9c9d93d738c855e652ce8</id>
<content type='text'>
The `+` is the one entry in the strip that does not mean "go here", and it
sits at the end of a list where every other entry does. Flush against them it
is what you hit when you meant the last tab -- most easily in a sidebar, where
the target is the full width of the row and the pointer only has to be one row
low. newtab_pad is the nothing in between: blank rows in a sidebar, blank
columns on top, one by default, which is enough to make the slip a miss.

It is paint rather than layout, like tab_bar_pad: the panes' rectangle is the
same at 0 as at 5, so tuning it repaints without reflowing anyone. And it is
given up before the button is -- a strip short of room drops the spacing one
cell at a time and still draws what the spacing was protecting, because
spacing must never cost you the thing it spaces.

newtab_button false removes the button outright, for people who make tabs from
the keyboard and would rather have the row (or the columns) back. Only the
place to click it goes: the chord and the palette entry still work. Setting
newtab_mark "" already did this by omission; this says it on purpose, and says
it in --dump-config.
</content>
</entry>
<entry>
<title>sidebar: framed like a pane, and in compact mode framed *with* it (tab_bar_chrome)</title>
<updated>2026-09-10T14:10:33Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-10T14:10:33Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=fca50377124202217c9cf5071d346f1bb30907bb'/>
<id>urn:sha1:fca50377124202217c9cf5071d346f1bb30907bb</id>
<content type='text'>
A list of tabs sitting in bare columns beside a framed layout reads as
text that wandered in from outside. So the sidebar gets a frame of its
own -- and in compact mode not its own: its inner vertical lands on the
tab area's ring column, and because the lines are strokes that union
with whatever is already in the cell, the corners become tees without
anyone computing a junction. The sidebar and the layout come out as one
figure, which is the whole point.

Classic mode gets a free-standing frame in the idle frame colour,
rounded or square with everything else. The frame costs a column each
side and a row top and bottom, taken from the sidebar's own budget --
and dropped entirely, before the list is, when the sidebar is too small
to afford it: chrome must not displace what it is chrome for.

The content box (cx/cw) now feeds the labels, the status rows, their
hits and the truncation budget alike, so the frame cannot disagree with
where the clicks land.
</content>
</entry>
<entry>
<title>docs: the tab bar's new shapes, written down</title>
<updated>2026-09-10T13:51:23Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-10T13:51:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=fa64070de57ba8cb916ae4273df5df9a3f47e212'/>
<id>urn:sha1:fa64070de57ba8cb916ae4273df5df9a3f47e212</id>
<content type='text'>
A 'The tab bar' section on the panes page: the sidebar, its four knobs,
the status rows under each tab, and the borrowed directory name for
unnamed tabs. The config page's knob table learns the tab_bar_* keys.

scripting.md and the driving-slosh skill stop underselling the status
verb: it was documented back when the pane's own frame was the only
place a status could appear, which made it decoration. With the
sidebar it is how a pane reports progress to somebody working in
another tab -- so say that, and say to keep it current and clear it
when the work is done.
</content>
</entry>
</feed>
